encode/httpx · error · InvalidURL

Invalid IPv6 address: {host!r}

Error message

Invalid IPv6 address: {host!r}

What it means

Raised by encode_host when the host is bracketed ([...]) indicating an IPv6 literal but ipaddress.IPv6Address() rejects the inner text. httpx expects the RFC3986 form '[address]' with a valid IPv6 inside; malformed addresses (bad hex, wrong group count, bad '::' usage) trigger InvalidURL.

Source

Thrown at httpx/_urlparse.py:376

        try:
            ipaddress.IPv4Address(host)
        except ipaddress.AddressValueError:
            raise InvalidURL(f"Invalid IPv4 address: {host!r}")
        return host

    elif IPv6_STYLE_HOSTNAME.match(host):
        # Validate IPv6 hostnames like [...]
        #
        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
        #
        # "A host identified by an Internet Protocol literal address, version 6
        # [RFC3513] or later, is distinguished by enclosing the IP literal
        # within square brackets ("[" and "]").  This is the only place where
        # square bracket characters are allowed in the URI syntax."
        try:
            ipaddress.IPv6Address(host[1:-1])
        except ipaddress.AddressValueError:
            raise InvalidURL(f"Invalid IPv6 address: {host!r}")
        return host[1:-1]

    elif host.isascii():
        # Regular ASCII hostnames
        #
        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
        #
        # reg-name    = *( unreserved / pct-encoded / sub-delims )
        WHATWG_SAFE = '"`{}%|\\'
        return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE)

    # IDNA hostnames
    try:
        return idna.encode(host.lower()).decode("ascii")
    except idna.IDNAError:
        raise InvalidURL(f"Invalid IDNA hostname: {host!r}")

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Validate with ipaddress.IPv6Address(host.strip('[]')) before constructing the URL.
  2. Use a single '::' shorthand; expand the address if unsure.
  3. For zone IDs (link-local), percent-encode: 'fe80::1%25eth0' inside the brackets.
  4. Generate IPv6 literals from ipaddress output rather than string concatenation.

Example fix

// before
url = httpx.URL("http://[fe80::1zz]/")  # Invalid IPv6 address

// after
import ipaddress
ipaddress.IPv6Address("fe80::1")  # validate first
url = httpx.URL("http://[fe80::1]/")
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress

def safe_ipv6(host: str) -> str:
    inner = host.strip("[]")
    ipaddress.IPv6Address(inner)  # raises AddressValueError early
    return f"[{inner}]"

url = httpx.URL(host=safe_ipv6(user_ipv6))

Type guard

import ipaddress

def is_valid_ipv6(host: str) -> bool:
    try:
        ipaddress.IPv6Address(host.strip("[]"))
        return True
    except ipaddress.AddressValueError:
        return False

Try / catch

from httpx import InvalidURL

try:
    url = httpx.URL(f"http://[{ipv6}]/")
except InvalidURL as e:
    if "Invalid IPv6" in str(e):
        raise ValueError(f"Bad IPv6 literal: {ipv6!r}") from e
    raise

Prevention

When it happens

Trigger: httpx.URL('http://[fe80::1zz]/'), httpx.URL(host='[1234::5678::9abc]') (two '::'), or host='[::g]'.

Common situations: Hand-typing IPv6 literals; truncating or pasting partial addresses; forgetting that '::' may appear only once; mixing zone identifiers without '%25' encoding.

Related errors


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