encode/httpx · error · InvalidURL

Invalid port: {port!r}

Error message

Invalid port: {port!r}

What it means

Raised by normalize_port when the port string cannot be converted with int(port). Ports must be decimal integers; non-numeric strings (or floats-as-strings) are rejected as InvalidURL. Note a numeric-but-out-of-range port is NOT caught here (that surfaces from the OS at connect time).

Source

Thrown at httpx/_urlparse.py:411


def normalize_port(port: str | int | None, scheme: str) -> int | None:
    # From https://tools.ietf.org/html/rfc3986#section-3.2.3
    #
    # "A scheme may define a default port.  For example, the "http" scheme
    # defines a default port of "80", corresponding to its reserved TCP
    # port number.  The type of port designated by the port number (e.g.,
    # TCP, UDP, SCTP) is defined by the URI scheme.  URI producers and
    # normalizers should omit the port component and its ":" delimiter if
    # port is empty or if its value would be the same as that of the
    # scheme's default."
    if port is None or port == "":
        return None

    try:
        port_as_int = int(port)
    except ValueError:
        raise InvalidURL(f"Invalid port: {port!r}")

    # See https://url.spec.whatwg.org/#url-miscellaneous
    default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get(
        scheme
    )
    if port_as_int == default_port:
        return None
    return port_as_int


def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None:
    """
    Path validation rules that depend on if the URL contains
    a scheme or authority component.

    See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3
    """
    if has_authority:

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Coerce the port to int explicitly before passing: port = int(port_str.strip()).
  2. Validate the string with .isdecimal() and bounds 0 <= port <= 65535.
  3. Strip whitespace/newlines from config-derived port strings.
  4. Pass port as an int (the kwargs API accepts int) rather than a string.

Example fix

// before
port_from_config = "8080\n"
url = httpx.URL("http://example.com", port=port_from_config)  # Invalid port

// after
port = int(port_from_config.strip())
url = httpx.URL("http://example.com", port=port)
Defensive patterns

Strategy: validation

Validate before calling

def safe_port(port) -> int:
    if isinstance(port, str):
        port = port.strip()
    p = int(port)  # ValueError surfaces here with a clean traceback
    if not (0 <= p <= 65535):
        raise ValueError(f"Port out of range: {p}")
    return p

url = httpx.URL("http://example.com", port=safe_port(port_cfg))

Type guard

def is_valid_port(value) -> bool:
    try:
        return 0 <= int(str(value).strip()) <= 65535
    except (TypeError, ValueError):
        return False

Try / catch

from httpx import InvalidURL

try:
    url = httpx.URL("http://h", port=raw_port)
except InvalidURL as e:
    if "Invalid port" in str(e):
        raise ValueError(f"Bad port from config: {raw_port!r}") from e
    raise

Prevention

When it happens

Trigger: httpx.URL('http://host:abc/'), httpx.URL(host='h', port='8080.0'), or passing port as a stringified float.

Common situations: Reading the port from a config file as a string with a trailing newline or unit ('8080\n', '8080/tcp'); passing a float by mistake; user input 'https'.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/5013c9d9d28a644f.json. Report an issue: GitHub.