encode/httpx · error · InvalidURL

Invalid URL component '{key}'

Error message

Invalid URL component '{key}'

What it means

Raised during the kwargs validation loop when a component value fails to fully match its COMPONENT_REGEX. Each component has its own grammar (e.g. path must match '[^?#]*', scheme must match '([a-zA-Z][a-zA-Z0-9+.-]*)?'); a mismatch means the supplied value cannot be a valid component at all.

Source

Thrown at httpx/_urlparse.py:286

            if len(value) > MAX_URL_LENGTH:
                raise InvalidURL(f"URL component '{key}' too long")

            # If a component includes any ASCII control characters including \t, \r, \n,
            # then treat it as invalid.
            if any(char.isascii() and not char.isprintable() for char in value):
                char = next(
                    char for char in value if char.isascii() and not char.isprintable()
                )
                idx = value.find(char)
                error = (
                    f"Invalid non-printable ASCII character in URL {key} component, "
                    f"{char!r} at position {idx}."
                )
                raise InvalidURL(error)

            # Ensure that keyword arguments match as a valid regex.
            if not COMPONENT_REGEX[key].fullmatch(value):
                raise InvalidURL(f"Invalid URL component '{key}'")

    # The URL_REGEX will always match, but may have empty components.
    url_match = URL_REGEX.match(url)
    assert url_match is not None
    url_dict = url_match.groupdict()

    # * 'scheme', 'authority', and 'path' may be empty strings.
    # * 'query' may be 'None', indicating no trailing "?" portion.
    #   Any string including the empty string, indicates a trailing "?".
    # * 'fragment' may be 'None', indicating no trailing "#" portion.
    #   Any string including the empty string, indicates a trailing "#".
    scheme = kwargs.get("scheme", url_dict["scheme"]) or ""
    authority = kwargs.get("authority", url_dict["authority"]) or ""
    path = kwargs.get("path", url_dict["path"]) or ""
    query = kwargs.get("query", url_dict["query"])
    frag = kwargs.get("fragment", url_dict["fragment"])

    # The AUTHORITY_REGEX will always match, but may have empty components.

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Split mixed input into the correct components (use raw_path= to pass '/p?x=1' rather than path=).
  2. Validate the component against its expected grammar before constructing the URL.
  3. For scheme, ensure it starts with a letter and contains only [a-zA-Z0-9+.-].
  4. Use httpx.URL('full-string') and let httpx parse, rather than hand-building components.

Example fix

// before
url = httpx.URL(scheme="https", host="api.example.com", path="/items?page=1")  # Invalid URL component 'path'

// after
url = httpx.URL(scheme="https", host="api.example.com", raw_path="/items?page=1")
Defensive patterns

Strategy: validation

Validate before calling

import re

COMPONENT_REGEX = {
    "scheme": re.compile(r"([a-zA-Z][a-zA-Z0-9+.-]*)?"),
    "path": re.compile(r"[^?#]*"),
    "query": re.compile(r"[^#]*"),
    # ... etc
}

def validate_component(key: str, value: str) -> str:
    if not COMPONENT_REGEX[key].fullmatch(value):
        raise ValueError(f"Invalid URL component {key!r}")
    return value

Type guard

import re

def path_is_valid(path: str) -> bool:
    return re.fullmatch(r"[^?#]*", path) is not None

Try / catch

from httpx import InvalidURL

try:
    url = httpx.URL(scheme="https", host="x", raw_path=mixed)
except InvalidURL as e:
    if "Invalid URL component" in str(e):
        # split raw_path into path+query yourself, or use full-string URL
        url = httpx.URL(f"https://x{mixed}")
    else:
        raise

Prevention

When it happens

Trigger: httpx.URL(scheme='123bad') (scheme must start with a letter); httpx.URL(path='/a?b') (path must not contain '?'); httpx.URL(fragment='a#b') (fragment must not contain '#' as a sub-token... actually fragment regex is '.*' so any char passes, but other components are stricter).

Common situations: Programmatically building components without sanitization; passing a path that already includes a querystring instead of using raw_path/query; user input that breaks the scheme grammar.

Related errors


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