{"id":"f04ec0516abe299e","repo":"encode/httpx","slug":"proxy-keys-should-use-proper-url-forms-rather-than","errorCode":null,"errorMessage":"Proxy keys should use proper URL forms rather than plain scheme strings. Instead of \"{pattern}\", use \"{pattern}://\"","messagePattern":"Proxy keys should use proper URL forms rather than plain scheme strings\\. Instead of \"(.+?)\", use \"(.+?)://\"","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"httpx/_utils.py","lineNumber":166,"sourceCode":"    True\n    >>> pattern.matches(httpx.URL(\"http://example.com\"))\n    True\n    >>> pattern.matches(httpx.URL(\"https://other.com\"))\n    False\n\n    # With port matching...\n    >>> pattern = URLPattern(\"https://example.com:1234\")\n    >>> pattern.matches(httpx.URL(\"https://example.com:1234\"))\n    True\n    >>> pattern.matches(httpx.URL(\"https://example.com\"))\n    False\n    \"\"\"\n\n    def __init__(self, pattern: str) -> None:\n        from ._urls import URL\n\n        if pattern and \":\" not in pattern:\n            raise ValueError(\n                f\"Proxy keys should use proper URL forms rather \"\n                f\"than plain scheme strings. \"\n                f'Instead of \"{pattern}\", use \"{pattern}://\"'\n            )\n\n        url = URL(pattern)\n        self.pattern = pattern\n        self.scheme = \"\" if url.scheme == \"all\" else url.scheme\n        self.host = \"\" if url.host == \"*\" else url.host\n        self.port = url.port\n        if not url.host or url.host == \"*\":\n            self.host_regex: typing.Pattern[str] | None = None\n        elif url.host.startswith(\"*.\"):\n            # *.example.com should match \"www.example.com\", but not \"example.com\"\n            domain = re.escape(url.host[2:])\n            self.host_regex = re.compile(f\"^.+\\\\.{domain}$\")\n        elif url.host.startswith(\"*\"):\n            # *example.com should match \"www.example.com\" and \"example.com\"","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_utils.py#L148-L184","documentation":"httpx.URLPattern (used to match proxies and mounts) requires a proper URL-shaped pattern string. The constructor at httpx/_utils.py:165 checks if the pattern is non-empty and contains no ':' character; if so it raises ValueError telling you to turn a bare scheme like 'http' into a URL form like 'http://'. The validator exists because plain scheme strings would otherwise parse ambiguously or match nothing useful.","triggerScenarios":"Constructing httpx.URLPattern('http'), httpx.URLPattern('https'), or any non-empty string without a colon. Most commonly hit when configuring mounts/proxies with a dict whose keys are bare schemes: client = httpx.Client(mounts={'http': httpx.HTTPTransport(...)}) or Client(proxy=...) variants that route through URLPattern. The check fires before any URL parsing, so any colon-less non-empty pattern raises immediately.","commonSituations":"Configuring httpx proxies/mounts from examples that abbreviated the scheme, or from config files/env vars that store just 'http'/'https'/'all'. Migrating from requests-style proxies={'http': ...} (bare scheme keys are idiomatic there) to httpx mounts={'http://': ...}. Typos like 'httpto' or trailing tokens without '://'. Auto-generated patterns from a scheme list that forgot the '://' suffix.","solutions":["Suffix the bare scheme with '://': use 'http://', 'https://', or the wildcard 'all://' as the pattern/mount key.","For env-driven config, normalize at the boundary: pattern = scheme if '://' in scheme or ':' in scheme else scheme + '://'.","If passing a full URL pattern (e.g. 'https://example.com'), it already contains ':' and passes; only bare scheme strings fail.","Double-check mounts/proxy dict keys when porting requests proxies={'http': ...} to httpx mounts={'http://': ...}; the key format changed."],"exampleFix":"// before\nclient = httpx.Client(mounts={\n    'http': httpx.HTTPTransport(proxy='http://proxy:8080'),  # raises ValueError\n})\n\n// after\nclient = httpx.Client(mounts={\n    'http://': httpx.HTTPTransport(proxy='http://proxy:8080'),\n})","handlingStrategy":"validation","validationCode":"def normalize_proxy_key(pattern: str) -> str:\n    \"\"\"Ensure a proxy/mount key is a proper URL-shaped pattern.\n\n    'http' -> 'http://', 'all' -> 'all://', full URLs and wildcard\n    hosts pass through unchanged.\n    \"\"\"\n    if not pattern:\n        return pattern\n    if ':' in pattern:\n        return pattern\n    return f'{pattern}://'\n\n# Usage at the config boundary:\nraw = {'http': transport, 'all': transport}\nmounts = {normalize_proxy_key(k): v for k, v in raw.items()}","typeGuard":"def is_valid_urlpattern(pattern: str) -> bool:\n    \"\"\"True if pattern won't trip httpx.URLPattern's bare-scheme guard.\n\n    Empty patterns are allowed by httpx; non-empty must contain ':'.\n    \"\"\"\n    return pattern == '' or ':' in pattern","tryCatchPattern":"import httpx\n\ndef build_pattern(pattern: str) -> httpx.URLPattern:\n    try:\n        return httpx.URLPattern(pattern)\n    except ValueError:\n        # Bare scheme: retry with the URL form recommended by the error.\n        if pattern and ':' not in pattern:\n            return httpx.URLPattern(f'{pattern}://')\n        raise","preventionTips":["When porting requests proxies={'http': ...} to httpx, always rewrite keys to 'http://' / 'https://' / 'all://'.","Normalize scheme strings read from env/config at the trust boundary with the normalize_proxy_key helper above.","Keep proxy/mount keys in a single constants module so the '://' suffix can't be forgotten per call site.","Add a startup assert that every key in your mounts/proxies dict passes is_valid_urlpattern, so misconfigurations fail fast at boot, not at first request."],"tags":["httpx","url-pattern","proxy","mounts","config","validation"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}