importipaddressfromtypingimportAnnotatedfrompydanticimportAfterValidator_INVALID_HOST_MESSAGE="Value must be a valid IP address or hostname"def_normalize_host(value:str)->str:candidate=value.strip()ifnotcandidateorcandidate.lower().startswith(("http://","https://")):raiseValueError(_INVALID_HOST_MESSAGE)candidate=candidate.rstrip("/")ifnotcandidateorlen(candidate)>253:raiseValueError(_INVALID_HOST_MESSAGE)ip_candidate=candidateifcandidate.startswith("[")andcandidate.endswith("]"):ip_candidate=candidate[1:-1]try:returnipaddress.ip_address(ip_candidate).compressedexceptValueError:passif(notany(char.isalnum()forcharincandidate)orany(char.isspace()forcharincandidate)orany(charin"/:@?#[]\\"forcharincandidate)):raiseValueError(_INVALID_HOST_MESSAGE)returncandidate.removesuffix(".").lower()Host=Annotated[str,AfterValidator(_normalize_host)]"""A normalized IP address or hostname for use in Pydantic models."""
[docs]defformat_url_host(host:str)->str:"""Format a normalized host for use in a URL authority."""try:address=ipaddress.ip_address(host)exceptValueError:returnhostreturnf"[{address.compressed}]"ifaddress.version==6elseaddress.compressed