encode/httpx · error · TypeError

Argument {key!r} must be {expected} but got {seen}

Error message

Argument {key!r} must be {expected} but got {seen}

What it means

Raised by URL.__init__ when a kwarg key IS in the allowed set but the value's type does not match the declared type (e.g. port must be int, scheme/path must be str, userinfo/query/raw_path/netloc must be bytes). The check skips None. Message names the expected type and the actually-seen type.

Source

Thrown at httpx/_urls.py:103

                "port": int,
                "netloc": bytes,
                "path": str,
                "query": bytes,
                "raw_path": bytes,
                "fragment": str,
                "params": object,
            }

            # Perform type checking for all supported keyword arguments.
            for key, value in kwargs.items():
                if key not in allowed:
                    message = f"{key!r} is an invalid keyword argument for URL()"
                    raise TypeError(message)
                if value is not None and not isinstance(value, allowed[key]):
                    expected = allowed[key].__name__
                    seen = type(value).__name__
                    message = f"Argument {key!r} must be {expected} but got {seen}"
                    raise TypeError(message)
                if isinstance(value, bytes):
                    kwargs[key] = value.decode("ascii")

            if "params" in kwargs:
                # Replace any "params" keyword with the raw "query" instead.
                #
                # Ensure that empty params use `kwargs["query"] = None` rather
                # than `kwargs["query"] = ""`, so that generated URLs do not
                # include an empty trailing "?".
                params = kwargs.pop("params")
                kwargs["query"] = None if not params else str(QueryParams(params))

        if isinstance(url, str):
            self._uri_reference = urlparse(url, **kwargs)
        elif isinstance(url, URL):
            self._uri_reference = url._uri_reference.copy_with(**kwargs)
        else:
            raise TypeError(

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Match the documented types: str for scheme/username/password/host/path/fragment; int for port; bytes for userinfo/query/raw_path/netloc.
  2. Decode bytes to str (or encode str to bytes) explicitly before passing.
  3. Use the convenience aliases (username/password/netloc/raw_path/params) which httpx converts for you.
  4. Add a typed wrapper or dataclass that guarantees types reach URL().

Example fix

// before
url = httpx.URL(scheme=b"https", host="example.com")  # TypeError: scheme must be str

// after
url = httpx.URL(scheme="https", host="example.com")
Defensive patterns

Strategy: type-guard

Validate before calling

URL_KWARG_TYPES = {
    "scheme": str, "username": str, "password": str, "userinfo": bytes,
    "host": str, "port": int, "netloc": bytes, "path": str,
    "query": bytes, "raw_path": bytes, "fragment": str, "params": object,
}

def coerce_url_kwargs(kwargs: dict) -> dict:
    out = {}
    for k, v in kwargs.items():
        if v is None:
            out[k] = v
            continue
        expected = URL_KWARG_TYPES[k]
        if not isinstance(v, expected):
            if expected is str and isinstance(v, bytes):
                v = v.decode("ascii")
            elif expected is bytes and isinstance(v, str):
                v = v.encode("ascii")
            elif expected is int:
                v = int(v)
            else:
                raise TypeError(f"{k!r} must be {expected.__name__}")
        out[k] = v
    return out

Type guard

def url_kwargs_match_types(kwargs: dict) -> bool:
    types = {
        "scheme": str, "username": str, "password": str, "userinfo": bytes,
        "host": str, "port": int, "netloc": bytes, "path": str,
        "query": bytes, "raw_path": bytes, "fragment": str, "params": object,
    }
    return all(
        v is None or isinstance(v, types[k])
        for k, v in kwargs.items() if k in types
    )

Try / catch

try:
    url = httpx.URL(base, **kwargs)
except TypeError as e:
    if "must be" in str(e) and "but got" in str(e):
        raise TypeError(f"URL kwarg type mismatch: {e}") from e
    raise

Prevention

When it happens

Trigger: httpx.URL(port='8080') is fine (port is special-cased), but httpx.URL(scheme=b'https') (bytes for a str field), httpx.URL(host=123), httpx.URL(path=b'/x'), or httpx.URL(query='/x') (str for bytes field) raise.

Common situations: Passing bytes to a str-typed field after reading from a binary source; passing an int host; mixing up which fields are bytes (userinfo, query, raw_path, netloc) vs str (scheme, username, password, host, path, fragment).

Related errors


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