{"id":"71b09d39c56c9477","repo":"encode/httpx","slug":"queryparams-are-immutable-since-0-18-0-use-q-q-71b09d","errorCode":null,"errorMessage":"QueryParams are immutable since 0.18.0. Use `q = q.set(key, value)` to create an updated copy.","messagePattern":"QueryParams are immutable since 0\\.18\\.0\\. Use `q = q\\.set\\(key, value\\)` to create an updated copy\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"httpx/_urls.py","lineNumber":638,"sourceCode":"            return False\n        return sorted(self.multi_items()) == sorted(other.multi_items())\n\n    def __str__(self) -> str:\n        return urlencode(self.multi_items())\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        query_string = str(self)\n        return f\"{class_name}({query_string!r})\"\n\n    def update(self, params: QueryParamTypes | None = None) -> None:\n        raise RuntimeError(\n            \"QueryParams are immutable since 0.18.0. \"\n            \"Use `q = q.merge(...)` to create an updated copy.\"\n        )\n\n    def __setitem__(self, key: str, value: str) -> None:\n        raise RuntimeError(\n            \"QueryParams are immutable since 0.18.0. \"\n            \"Use `q = q.set(key, value)` to create an updated copy.\"\n        )\n","sourceCodeStart":620,"sourceCodeEnd":642,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_urls.py#L620-L642","documentation":"Since httpx 0.18.0, QueryParams is immutable, so item assignment via __setitem__ (q[key] = value) is disabled and raises RuntimeError. The functional replacement is q = q.set(key, value), which copies the internal dict, overwrites the single key, and returns a new QueryParams instance. Existing keys are replaced; keys not present are added.","triggerScenarios":"Any item-assignment on a QueryParams instance, e.g. q = httpx.QueryParams('a=1'); q['b'] = '2'. The raise at httpx/_urls.py:638 is unconditional inside __setitem__, so every assignment hits it. Also triggered by generic dict-style helpers that do params[k] = v when handed a QueryParams.","commonSituations":"Code migrated from requests or older httpx where query params were treated as mutable dicts. Tutorial/blog code predating 0.18.0. Generic web helpers that uniformly mutate whatever Mapping they receive. Type-checkers won't catch it because __setitem__ is typed to accept the assignment.","solutions":["Replace params[key] = value with params = params.set(key, value); set returns a new instance with that key overwritten (or added if absent).","For bulk replacement, collect into a dict and use params = params.merge({...}) once, rather than many chained .set() calls.","If you need append semantics (multi-value key), use params = params.add(key, value) instead of set.","Audit generic helpers that accept Mapping and mutate via [] =; branch on isinstance(x, httpx.QueryParams) or require callers to pass plain dicts that you convert at the boundary."],"exampleFix":"// before\nq = httpx.QueryParams('a=1')\nq['b'] = '2'  # raises RuntimeError\n\n// after\nq = httpx.QueryParams('a=1')\nq = q.set('b', '2')  # new immutable QueryParams('a=1&b=2')","handlingStrategy":"validation","validationCode":"import httpx\n\ndef safe_set(q, key, value):\n    \"\"\"Set a single key on q, returning a new QueryParams.\n\n    Never uses __setitem__, so the immutability guard cannot fire.\n    \"\"\"\n    if not isinstance(q, httpx.QueryParams):\n        q = httpx.QueryParams(q)\n    return q.set(key, value)","typeGuard":"import httpx\n\ndef is_immutable_queryparams(q) -> bool:\n    \"\"\"True when q is a >=0.18.0 QueryParams (item assignment raises).\"\"\"\n    return isinstance(q, httpx.QueryParams)","tryCatchPattern":"import httpx\n\ndef set_or_fallback(q, key, value):\n    try:\n        return q.set(key, value)\n    except (RuntimeError, AttributeError):\n        items = dict(q.multi_items())\n        items[key] = value\n        return httpx.QueryParams(items)","preventionTips":["Never use params[k] = v on a QueryParams; always params = params.set(k, v).","If a generic helper accepts a Mapping and mutates it, convert httpx.QueryParams to a plain dict at the boundary, mutate, then rebuild: httpx.QueryParams(dict(q.multi_items())).","Type-annotate params as httpx.QueryParams (not dict) so reviewers spot mutation attempts.","Add a unit test asserting set/merge return new instances; catches regressions if someone reintroduces __setitem__ use."],"tags":["httpx","query-params","immutability","migration","runtime-error"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}