prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Change availability endpoint to not require a user to be logged in
  ### Issue description and context
  
  Current api/v1/available requires a user to be logged in, even though the login required tag was removed already. Troubleshoot this and ensure that a non-logged in user can access the availability endpoint (api/v1/available)
  
  ### Acceptance criteria
  
  - [ ] a user not logged in can access api/v1/available/<domain> and will see the response to whether the domain string passed is in fact available.
  
  ### Links to other issues
  
  Blocks: #476
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/api/views.py`
  Content:
  ```
  1 """Internal API views"""
  2 from django.apps import apps
  3 from django.views.decorators.http import require_http_methods
  4 from django.http import JsonResponse
  5 
  6 import requests
  7 
  8 from cachetools.func import ttl_cache
  9 
  10 
  11 DOMAIN_FILE_URL = (
  12     "https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
  13 )
  14 
  15 
  16 DOMAIN_API_MESSAGES = {
  17     "required": "Enter the .gov domain you want. Don’t include “www” or “.gov.”"
  18     " For example, if you want www.city.gov, you would enter “city”"
  19     " (without the quotes).",
  20     "extra_dots": "Enter the .gov domain you want without any periods.",
  21     "unavailable": "That domain isn’t available. Try entering another one."
  22     " Contact us if you need help coming up with a domain.",
  23     "invalid": "Enter a domain using only letters,"
  24     " numbers, or hyphens (though we don't recommend using hyphens).",
  25     "success": "That domain is available!",
  26 }
  27 
  28 
  29 # this file doesn't change that often, nor is it that big, so cache the result
  30 # in memory for ten minutes
  31 @ttl_cache(ttl=600)
  32 def _domains():
  33     """Return a list of the current .gov domains.
  34 
  35     Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,
  36     lowercase everything and return the list.
  37     """
  38     DraftDomain = apps.get_model("registrar.DraftDomain")
  39     # 5 second timeout
  40     file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text
  41     domains = set()
  42     # skip the first line
  43     for line in file_contents.splitlines()[1:]:
  44         # get the domain before the first comma
  45         domain = line.split(",", 1)[0]
  46         # sanity-check the string we got from the file here
  47         if DraftDomain.string_could_be_domain(domain):
  48             # lowercase everything when we put it in domains
  49             domains.add(domain.lower())
  50     return domains
  51 
  52 
  53 def in_domains(domain):
  54     """Return true if the given domain is in the domains list.
  55 
  56     The given domain is lowercased to match against the domains list. If the
  57     given domain doesn't end with .gov, ".gov" is added when looking for
  58     a match.
  59     """
  60     Domain = apps.get_model("registrar.Domain")
  61     if domain.endswith(".gov"):
  62         return Domain.available(domain)
  63     else:
  64         # domain search string doesn't end with .gov, add it on here
  65         return Domain.available(domain + ".gov")
  66 
  67 
  68 @require_http_methods(["GET"])
  69 def available(request, domain=""):
  70     """Is a given domain available or not.
  71 
  72     Response is a JSON dictionary with the key "available" and value true or
  73     false.
  74     """
  75     DraftDomain = apps.get_model("registrar.DraftDomain")
  76     # validate that the given domain could be a domain name and fail early if
  77     # not.
  78     if not (
  79         DraftDomain.string_could_be_domain(domain)
  80         or DraftDomain.string_could_be_domain(domain + ".gov")
  81     ):
  82         return JsonResponse(
  83             {"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
  84         )
  85     # a domain is available if it is NOT in the list of current domains
  86     if in_domains(domain):
  87         return JsonResponse(
  88             {"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
  89         )
  90     else:
  91         return JsonResponse(
  92             {"available": True, "message": DOMAIN_API_MESSAGES["success"]}
  93         )
  94
  ```
  
  Path: `src/registrar/models/utility/domain_helper.py`
  Content:
  ```
  1 import re
  2 
  3 from api.views import in_domains
  4 from registrar.utility import errors
  5 
  6 
  7 class DomainHelper:
  8     """Utility functions and constants for domain names."""
  9 
  10     # a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
  11     # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
  12     DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}$")
  13 
  14     # a domain name is alphanumeric or hyphen, has at least 2 dots, doesn't
  15     # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
  16     HOST_REGEX = re.compile(r"^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.){2,}([A-Za-z]){2,6}$")
  17 
  18     # a domain can be no longer than 253 characters in total
  19     MAX_LENGTH = 253
  20 
  21     @classmethod
  22     def string_could_be_domain(cls, domain: str | None) -> bool:
  23         """Return True if the string could be a domain name, otherwise False."""
  24         if not isinstance(domain, str):
  25             return False
  26         return bool(cls.DOMAIN_REGEX.match(domain))
  27 
  28     @classmethod
  29     def validate(cls, domain: str | None, blank_ok=False) -> str:
  30         """Attempt to determine if a domain name could be requested."""
  31         if domain is None:
  32             raise errors.BlankValueError()
  33         if not isinstance(domain, str):
  34             raise ValueError("Domain name must be a string")
  35         domain = domain.lower().strip()
  36         if domain == "":
  37             if blank_ok:
  38                 return domain
  39             else:
  40                 raise errors.BlankValueError()
  41         if domain.endswith(".gov"):
  42             domain = domain[:-4]
  43         if "." in domain:
  44             raise errors.ExtraDotsError()
  45         if not DomainHelper.string_could_be_domain(domain + ".gov"):
  46             raise ValueError()
  47         if in_domains(domain):
  48             raise errors.DomainUnavailableError()
  49         return domain
  50 
  51     @classmethod
  52     def sld(cls, domain: str):
  53         """
  54         Get the second level domain. Example: `gsa.gov` -> `gsa`.
  55 
  56         If no TLD is present, returns the original string.
  57         """
  58         return domain.split(".")[0]
  59 
  60     @classmethod
  61     def tld(cls, domain: str):
  62         """Get the top level domain. Example: `gsa.gov` -> `gov`."""
  63         parts = domain.rsplit(".")
  64         return parts[-1] if len(parts) > 1 else ""
  65
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/api/views.py b/src/api/views.py
  --- a/src/api/views.py
  +++ b/src/api/views.py
  @@ -5,6 +5,8 @@
   
   import requests
   
  +from login_required import login_not_required
  +
   from cachetools.func import ttl_cache
   
   
  @@ -23,6 +25,7 @@
       "invalid": "Enter a domain using only letters,"
       " numbers, or hyphens (though we don't recommend using hyphens).",
       "success": "That domain is available!",
  +    "error": "Error finding domain availability.",
   }
   
   
  @@ -50,22 +53,26 @@
       return domains
   
   
  -def in_domains(domain):
  -    """Return true if the given domain is in the domains list.
  +def check_domain_available(domain):
  +    """Return true if the given domain is available.
   
       The given domain is lowercased to match against the domains list. If the
       given domain doesn't end with .gov, ".gov" is added when looking for
       a match.
       """
       Domain = apps.get_model("registrar.Domain")
  -    if domain.endswith(".gov"):
  -        return Domain.available(domain)
  -    else:
  -        # domain search string doesn't end with .gov, add it on here
  -        return Domain.available(domain + ".gov")
  +    try:
  +        if domain.endswith(".gov"):
  +            return Domain.available(domain)
  +        else:
  +            # domain search string doesn't end with .gov, add it on here
  +            return Domain.available(domain + ".gov")
  +    except Exception:
  +        return False
   
   
   @require_http_methods(["GET"])
  +@login_not_required
   def available(request, domain=""):
       """Is a given domain available or not.
   
  @@ -83,11 +90,16 @@
               {"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
           )
       # a domain is available if it is NOT in the list of current domains
  -    if in_domains(domain):
  -        return JsonResponse(
  -            {"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
  -        )
  -    else:
  +    try:
  +        if check_domain_available(domain):
  +            return JsonResponse(
  +                {"available": True, "message": DOMAIN_API_MESSAGES["success"]}
  +            )
  +        else:
  +            return JsonResponse(
  +                {"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
  +            )
  +    except Exception:
           return JsonResponse(
  -            {"available": True, "message": DOMAIN_API_MESSAGES["success"]}
  +            {"available": False, "message": DOMAIN_API_MESSAGES["error"]}
           )
  diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py
  --- a/src/registrar/models/utility/domain_helper.py
  +++ b/src/registrar/models/utility/domain_helper.py
  @@ -1,6 +1,6 @@
   import re
   
  -from api.views import in_domains
  +from api.views import check_domain_available
   from registrar.utility import errors
   
   
  @@ -44,7 +44,7 @@
               raise errors.ExtraDotsError()
           if not DomainHelper.string_could_be_domain(domain + ".gov"):
               raise ValueError()
  -        if in_domains(domain):
  +        if not check_domain_available(domain):
               raise errors.DomainUnavailableError()
           return domain
  
verification_info: {"golden_diff": "diff --git a/src/api/views.py b/src/api/views.py\n--- a/src/api/views.py\n+++ b/src/api/views.py\n@@ -5,6 +5,8 @@\n \n import requests\n \n+from login_required import login_not_required\n+\n from cachetools.func import ttl_cache\n \n \n@@ -23,6 +25,7 @@\n     \"invalid\": \"Enter a domain using only letters,\"\n     \" numbers, or hyphens (though we don't recommend using hyphens).\",\n     \"success\": \"That domain is available!\",\n+    \"error\": \"Error finding domain availability.\",\n }\n \n \n@@ -50,22 +53,26 @@\n     return domains\n \n \n-def in_domains(domain):\n-    \"\"\"Return true if the given domain is in the domains list.\n+def check_domain_available(domain):\n+    \"\"\"Return true if the given domain is available.\n \n     The given domain is lowercased to match against the domains list. If the\n     given domain doesn't end with .gov, \".gov\" is added when looking for\n     a match.\n     \"\"\"\n     Domain = apps.get_model(\"registrar.Domain\")\n-    if domain.endswith(\".gov\"):\n-        return Domain.available(domain)\n-    else:\n-        # domain search string doesn't end with .gov, add it on here\n-        return Domain.available(domain + \".gov\")\n+    try:\n+        if domain.endswith(\".gov\"):\n+            return Domain.available(domain)\n+        else:\n+            # domain search string doesn't end with .gov, add it on here\n+            return Domain.available(domain + \".gov\")\n+    except Exception:\n+        return False\n \n \n @require_http_methods([\"GET\"])\n+@login_not_required\n def available(request, domain=\"\"):\n     \"\"\"Is a given domain available or not.\n \n@@ -83,11 +90,16 @@\n             {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"invalid\"]}\n         )\n     # a domain is available if it is NOT in the list of current domains\n-    if in_domains(domain):\n-        return JsonResponse(\n-            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"unavailable\"]}\n-        )\n-    else:\n+    try:\n+        if check_domain_available(domain):\n+            return JsonResponse(\n+                {\"available\": True, \"message\": DOMAIN_API_MESSAGES[\"success\"]}\n+            )\n+        else:\n+            return JsonResponse(\n+                {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"unavailable\"]}\n+            )\n+    except Exception:\n         return JsonResponse(\n-            {\"available\": True, \"message\": DOMAIN_API_MESSAGES[\"success\"]}\n+            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"error\"]}\n         )\ndiff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py\n--- a/src/registrar/models/utility/domain_helper.py\n+++ b/src/registrar/models/utility/domain_helper.py\n@@ -1,6 +1,6 @@\n import re\n \n-from api.views import in_domains\n+from api.views import check_domain_available\n from registrar.utility import errors\n \n \n@@ -44,7 +44,7 @@\n             raise errors.ExtraDotsError()\n         if not DomainHelper.string_could_be_domain(domain + \".gov\"):\n             raise ValueError()\n-        if in_domains(domain):\n+        if not check_domain_available(domain):\n             raise errors.DomainUnavailableError()\n         return domain\n", "issue": "Change availability endpoint to not require a user to be logged in\n### Issue description and context\n\nCurrent api/v1/available requires a user to be logged in, even though the login required tag was removed already. Troubleshoot this and ensure that a non-logged in user can access the availability endpoint (api/v1/available)\n\n### Acceptance criteria\n\n- [ ] a user not logged in can access api/v1/available/<domain> and will see the response to whether the domain string passed is in fact available.\n\n### Links to other issues\n\nBlocks: #476\n", "before_files": [{"content": "\"\"\"Internal API views\"\"\"\nfrom django.apps import apps\nfrom django.views.decorators.http import require_http_methods\nfrom django.http import JsonResponse\n\nimport requests\n\nfrom cachetools.func import ttl_cache\n\n\nDOMAIN_FILE_URL = (\n    \"https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv\"\n)\n\n\nDOMAIN_API_MESSAGES = {\n    \"required\": \"Enter the .gov domain you want. Don\u2019t include \u201cwww\u201d or \u201c.gov.\u201d\"\n    \" For example, if you want www.city.gov, you would enter \u201ccity\u201d\"\n    \" (without the quotes).\",\n    \"extra_dots\": \"Enter the .gov domain you want without any periods.\",\n    \"unavailable\": \"That domain isn\u2019t available. Try entering another one.\"\n    \" Contact us if you need help coming up with a domain.\",\n    \"invalid\": \"Enter a domain using only letters,\"\n    \" numbers, or hyphens (though we don't recommend using hyphens).\",\n    \"success\": \"That domain is available!\",\n}\n\n\n# this file doesn't change that often, nor is it that big, so cache the result\n# in memory for ten minutes\n@ttl_cache(ttl=600)\ndef _domains():\n    \"\"\"Return a list of the current .gov domains.\n\n    Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,\n    lowercase everything and return the list.\n    \"\"\"\n    DraftDomain = apps.get_model(\"registrar.DraftDomain\")\n    # 5 second timeout\n    file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text\n    domains = set()\n    # skip the first line\n    for line in file_contents.splitlines()[1:]:\n        # get the domain before the first comma\n        domain = line.split(\",\", 1)[0]\n        # sanity-check the string we got from the file here\n        if DraftDomain.string_could_be_domain(domain):\n            # lowercase everything when we put it in domains\n            domains.add(domain.lower())\n    return domains\n\n\ndef in_domains(domain):\n    \"\"\"Return true if the given domain is in the domains list.\n\n    The given domain is lowercased to match against the domains list. If the\n    given domain doesn't end with .gov, \".gov\" is added when looking for\n    a match.\n    \"\"\"\n    Domain = apps.get_model(\"registrar.Domain\")\n    if domain.endswith(\".gov\"):\n        return Domain.available(domain)\n    else:\n        # domain search string doesn't end with .gov, add it on here\n        return Domain.available(domain + \".gov\")\n\n\n@require_http_methods([\"GET\"])\ndef available(request, domain=\"\"):\n    \"\"\"Is a given domain available or not.\n\n    Response is a JSON dictionary with the key \"available\" and value true or\n    false.\n    \"\"\"\n    DraftDomain = apps.get_model(\"registrar.DraftDomain\")\n    # validate that the given domain could be a domain name and fail early if\n    # not.\n    if not (\n        DraftDomain.string_could_be_domain(domain)\n        or DraftDomain.string_could_be_domain(domain + \".gov\")\n    ):\n        return JsonResponse(\n            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"invalid\"]}\n        )\n    # a domain is available if it is NOT in the list of current domains\n    if in_domains(domain):\n        return JsonResponse(\n            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"unavailable\"]}\n        )\n    else:\n        return JsonResponse(\n            {\"available\": True, \"message\": DOMAIN_API_MESSAGES[\"success\"]}\n        )\n", "path": "src/api/views.py"}, {"content": "import re\n\nfrom api.views import in_domains\nfrom registrar.utility import errors\n\n\nclass DomainHelper:\n    \"\"\"Utility functions and constants for domain names.\"\"\"\n\n    # a domain name is alphanumeric or hyphen, up to 63 characters, doesn't\n    # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters\n    DOMAIN_REGEX = re.compile(r\"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.[A-Za-z]{2,6}$\")\n\n    # a domain name is alphanumeric or hyphen, has at least 2 dots, doesn't\n    # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters\n    HOST_REGEX = re.compile(r\"^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.){2,}([A-Za-z]){2,6}$\")\n\n    # a domain can be no longer than 253 characters in total\n    MAX_LENGTH = 253\n\n    @classmethod\n    def string_could_be_domain(cls, domain: str | None) -> bool:\n        \"\"\"Return True if the string could be a domain name, otherwise False.\"\"\"\n        if not isinstance(domain, str):\n            return False\n        return bool(cls.DOMAIN_REGEX.match(domain))\n\n    @classmethod\n    def validate(cls, domain: str | None, blank_ok=False) -> str:\n        \"\"\"Attempt to determine if a domain name could be requested.\"\"\"\n        if domain is None:\n            raise errors.BlankValueError()\n        if not isinstance(domain, str):\n            raise ValueError(\"Domain name must be a string\")\n        domain = domain.lower().strip()\n        if domain == \"\":\n            if blank_ok:\n                return domain\n            else:\n                raise errors.BlankValueError()\n        if domain.endswith(\".gov\"):\n            domain = domain[:-4]\n        if \".\" in domain:\n            raise errors.ExtraDotsError()\n        if not DomainHelper.string_could_be_domain(domain + \".gov\"):\n            raise ValueError()\n        if in_domains(domain):\n            raise errors.DomainUnavailableError()\n        return domain\n\n    @classmethod\n    def sld(cls, domain: str):\n        \"\"\"\n        Get the second level domain. Example: `gsa.gov` -> `gsa`.\n\n        If no TLD is present, returns the original string.\n        \"\"\"\n        return domain.split(\".\")[0]\n\n    @classmethod\n    def tld(cls, domain: str):\n        \"\"\"Get the top level domain. Example: `gsa.gov` -> `gov`.\"\"\"\n        parts = domain.rsplit(\".\")\n        return parts[-1] if len(parts) > 1 else \"\"\n", "path": "src/registrar/models/utility/domain_helper.py"}], "after_files": [{"content": "\"\"\"Internal API views\"\"\"\nfrom django.apps import apps\nfrom django.views.decorators.http import require_http_methods\nfrom django.http import JsonResponse\n\nimport requests\n\nfrom login_required import login_not_required\n\nfrom cachetools.func import ttl_cache\n\n\nDOMAIN_FILE_URL = (\n    \"https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv\"\n)\n\n\nDOMAIN_API_MESSAGES = {\n    \"required\": \"Enter the .gov domain you want. Don\u2019t include \u201cwww\u201d or \u201c.gov.\u201d\"\n    \" For example, if you want www.city.gov, you would enter \u201ccity\u201d\"\n    \" (without the quotes).\",\n    \"extra_dots\": \"Enter the .gov domain you want without any periods.\",\n    \"unavailable\": \"That domain isn\u2019t available. Try entering another one.\"\n    \" Contact us if you need help coming up with a domain.\",\n    \"invalid\": \"Enter a domain using only letters,\"\n    \" numbers, or hyphens (though we don't recommend using hyphens).\",\n    \"success\": \"That domain is available!\",\n    \"error\": \"Error finding domain availability.\",\n}\n\n\n# this file doesn't change that often, nor is it that big, so cache the result\n# in memory for ten minutes\n@ttl_cache(ttl=600)\ndef _domains():\n    \"\"\"Return a list of the current .gov domains.\n\n    Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,\n    lowercase everything and return the list.\n    \"\"\"\n    DraftDomain = apps.get_model(\"registrar.DraftDomain\")\n    # 5 second timeout\n    file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text\n    domains = set()\n    # skip the first line\n    for line in file_contents.splitlines()[1:]:\n        # get the domain before the first comma\n        domain = line.split(\",\", 1)[0]\n        # sanity-check the string we got from the file here\n        if DraftDomain.string_could_be_domain(domain):\n            # lowercase everything when we put it in domains\n            domains.add(domain.lower())\n    return domains\n\n\ndef check_domain_available(domain):\n    \"\"\"Return true if the given domain is available.\n\n    The given domain is lowercased to match against the domains list. If the\n    given domain doesn't end with .gov, \".gov\" is added when looking for\n    a match.\n    \"\"\"\n    Domain = apps.get_model(\"registrar.Domain\")\n    try:\n        if domain.endswith(\".gov\"):\n            return Domain.available(domain)\n        else:\n            # domain search string doesn't end with .gov, add it on here\n            return Domain.available(domain + \".gov\")\n    except Exception:\n        return False\n\n\n@require_http_methods([\"GET\"])\n@login_not_required\ndef available(request, domain=\"\"):\n    \"\"\"Is a given domain available or not.\n\n    Response is a JSON dictionary with the key \"available\" and value true or\n    false.\n    \"\"\"\n    DraftDomain = apps.get_model(\"registrar.DraftDomain\")\n    # validate that the given domain could be a domain name and fail early if\n    # not.\n    if not (\n        DraftDomain.string_could_be_domain(domain)\n        or DraftDomain.string_could_be_domain(domain + \".gov\")\n    ):\n        return JsonResponse(\n            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"invalid\"]}\n        )\n    # a domain is available if it is NOT in the list of current domains\n    try:\n        if check_domain_available(domain):\n            return JsonResponse(\n                {\"available\": True, \"message\": DOMAIN_API_MESSAGES[\"success\"]}\n            )\n        else:\n            return JsonResponse(\n                {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"unavailable\"]}\n            )\n    except Exception:\n        return JsonResponse(\n            {\"available\": False, \"message\": DOMAIN_API_MESSAGES[\"error\"]}\n        )\n", "path": "src/api/views.py"}, {"content": "import re\n\nfrom api.views import check_domain_available\nfrom registrar.utility import errors\n\n\nclass DomainHelper:\n    \"\"\"Utility functions and constants for domain names.\"\"\"\n\n    # a domain name is alphanumeric or hyphen, up to 63 characters, doesn't\n    # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters\n    DOMAIN_REGEX = re.compile(r\"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.[A-Za-z]{2,6}$\")\n\n    # a domain name is alphanumeric or hyphen, has at least 2 dots, doesn't\n    # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters\n    HOST_REGEX = re.compile(r\"^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.){2,}([A-Za-z]){2,6}$\")\n\n    # a domain can be no longer than 253 characters in total\n    MAX_LENGTH = 253\n\n    @classmethod\n    def string_could_be_domain(cls, domain: str | None) -> bool:\n        \"\"\"Return True if the string could be a domain name, otherwise False.\"\"\"\n        if not isinstance(domain, str):\n            return False\n        return bool(cls.DOMAIN_REGEX.match(domain))\n\n    @classmethod\n    def validate(cls, domain: str | None, blank_ok=False) -> str:\n        \"\"\"Attempt to determine if a domain name could be requested.\"\"\"\n        if domain is None:\n            raise errors.BlankValueError()\n        if not isinstance(domain, str):\n            raise ValueError(\"Domain name must be a string\")\n        domain = domain.lower().strip()\n        if domain == \"\":\n            if blank_ok:\n                return domain\n            else:\n                raise errors.BlankValueError()\n        if domain.endswith(\".gov\"):\n            domain = domain[:-4]\n        if \".\" in domain:\n            raise errors.ExtraDotsError()\n        if not DomainHelper.string_could_be_domain(domain + \".gov\"):\n            raise ValueError()\n        if not check_domain_available(domain):\n            raise errors.DomainUnavailableError()\n        return domain\n\n    @classmethod\n    def sld(cls, domain: str):\n        \"\"\"\n        Get the second level domain. Example: `gsa.gov` -> `gsa`.\n\n        If no TLD is present, returns the original string.\n        \"\"\"\n        return domain.split(\".\")[0]\n\n    @classmethod\n    def tld(cls, domain: str):\n        \"\"\"Get the top level domain. Example: `gsa.gov` -> `gov`.\"\"\"\n        parts = domain.rsplit(\".\")\n        return parts[-1] if len(parts) > 1 else \"\"\n", "path": "src/registrar/models/utility/domain_helper.py"}]}