{"id":"8730261d131b2118","repo":"encode/httpx","slug":"argument-key-r-must-be-expected-but-got-seen","errorCode":null,"errorMessage":"Argument {key!r} must be {expected} but got {seen}","messagePattern":"Argument (.+?) must be (.+?) but got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"httpx/_urls.py","lineNumber":103,"sourceCode":"                \"port\": int,\n                \"netloc\": bytes,\n                \"path\": str,\n                \"query\": bytes,\n                \"raw_path\": bytes,\n                \"fragment\": str,\n                \"params\": object,\n            }\n\n            # Perform type checking for all supported keyword arguments.\n            for key, value in kwargs.items():\n                if key not in allowed:\n                    message = f\"{key!r} is an invalid keyword argument for URL()\"\n                    raise TypeError(message)\n                if value is not None and not isinstance(value, allowed[key]):\n                    expected = allowed[key].__name__\n                    seen = type(value).__name__\n                    message = f\"Argument {key!r} must be {expected} but got {seen}\"\n                    raise TypeError(message)\n                if isinstance(value, bytes):\n                    kwargs[key] = value.decode(\"ascii\")\n\n            if \"params\" in kwargs:\n                # Replace any \"params\" keyword with the raw \"query\" instead.\n                #\n                # Ensure that empty params use `kwargs[\"query\"] = None` rather\n                # than `kwargs[\"query\"] = \"\"`, so that generated URLs do not\n                # include an empty trailing \"?\".\n                params = kwargs.pop(\"params\")\n                kwargs[\"query\"] = None if not params else str(QueryParams(params))\n\n        if isinstance(url, str):\n            self._uri_reference = urlparse(url, **kwargs)\n        elif isinstance(url, URL):\n            self._uri_reference = url._uri_reference.copy_with(**kwargs)\n        else:\n            raise TypeError(","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_urls.py#L85-L121","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Match the documented types: str for scheme/username/password/host/path/fragment; int for port; bytes for userinfo/query/raw_path/netloc.","Decode bytes to str (or encode str to bytes) explicitly before passing.","Use the convenience aliases (username/password/netloc/raw_path/params) which httpx converts for you.","Add a typed wrapper or dataclass that guarantees types reach URL()."],"exampleFix":"// before\nurl = httpx.URL(scheme=b\"https\", host=\"example.com\")  # TypeError: scheme must be str\n\n// after\nurl = httpx.URL(scheme=\"https\", host=\"example.com\")","handlingStrategy":"type-guard","validationCode":"URL_KWARG_TYPES = {\n    \"scheme\": str, \"username\": str, \"password\": str, \"userinfo\": bytes,\n    \"host\": str, \"port\": int, \"netloc\": bytes, \"path\": str,\n    \"query\": bytes, \"raw_path\": bytes, \"fragment\": str, \"params\": object,\n}\n\ndef coerce_url_kwargs(kwargs: dict) -> dict:\n    out = {}\n    for k, v in kwargs.items():\n        if v is None:\n            out[k] = v\n            continue\n        expected = URL_KWARG_TYPES[k]\n        if not isinstance(v, expected):\n            if expected is str and isinstance(v, bytes):\n                v = v.decode(\"ascii\")\n            elif expected is bytes and isinstance(v, str):\n                v = v.encode(\"ascii\")\n            elif expected is int:\n                v = int(v)\n            else:\n                raise TypeError(f\"{k!r} must be {expected.__name__}\")\n        out[k] = v\n    return out","typeGuard":"def url_kwargs_match_types(kwargs: dict) -> bool:\n    types = {\n        \"scheme\": str, \"username\": str, \"password\": str, \"userinfo\": bytes,\n        \"host\": str, \"port\": int, \"netloc\": bytes, \"path\": str,\n        \"query\": bytes, \"raw_path\": bytes, \"fragment\": str, \"params\": object,\n    }\n    return all(\n        v is None or isinstance(v, types[k])\n        for k, v in kwargs.items() if k in types\n    )","tryCatchPattern":"try:\n    url = httpx.URL(base, **kwargs)\nexcept TypeError as e:\n    if \"must be\" in str(e) and \"but got\" in str(e):\n        raise TypeError(f\"URL kwarg type mismatch: {e}\") from e\n    raise","preventionTips":["Memorize the bytes-vs-str split: userinfo/query/raw_path/netloc are bytes.","Decode/encode at the boundary so the right type reaches URL().","Add a thin typed wrapper around URL construction in shared code."],"tags":["url","type-mismatch","validation","typeerror"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}