encode/httpx · error · RuntimeError
QueryParams are immutable since 0.18.0. Use `q = q.set(key,
Error message
QueryParams are immutable since 0.18.0. Use `q = q.set(key, value)` to create an updated copy.
What it means
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.
Source
Thrown at httpx/_urls.py:638
return False
return sorted(self.multi_items()) == sorted(other.multi_items())
def __str__(self) -> str:
return urlencode(self.multi_items())
def __repr__(self) -> str:
class_name = self.__class__.__name__
query_string = str(self)
return f"{class_name}({query_string!r})"
def update(self, params: QueryParamTypes | None = None) -> None:
raise RuntimeError(
"QueryParams are immutable since 0.18.0. "
"Use `q = q.merge(...)` to create an updated copy."
)
def __setitem__(self, key: str, value: str) -> None:
raise RuntimeError(
"QueryParams are immutable since 0.18.0. "
"Use `q = q.set(key, value)` to create an updated copy."
)
View on GitHub (pinned to b5addb64f0)
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.
Example fix
// before
q = httpx.QueryParams('a=1')
q['b'] = '2' # raises RuntimeError
// after
q = httpx.QueryParams('a=1')
q = q.set('b', '2') # new immutable QueryParams('a=1&b=2') Defensive patterns
Strategy: validation
Validate before calling
import httpx
def safe_set(q, key, value):
"""Set a single key on q, returning a new QueryParams.
Never uses __setitem__, so the immutability guard cannot fire.
"""
if not isinstance(q, httpx.QueryParams):
q = httpx.QueryParams(q)
return q.set(key, value) Type guard
import httpx
def is_immutable_queryparams(q) -> bool:
"""True when q is a >=0.18.0 QueryParams (item assignment raises)."""
return isinstance(q, httpx.QueryParams) Try / catch
import httpx
def set_or_fallback(q, key, value):
try:
return q.set(key, value)
except (RuntimeError, AttributeError):
items = dict(q.multi_items())
items[key] = value
return httpx.QueryParams(items) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- QueryParams are immutable since 0.18.0. Use `q = q.merge(...
- The request instance has not been set on this response.
- Attempted to access streaming response content, without havi
- Cannot call `raise_for_status` as the request instance has n
- Attempted to read or stream some content, but the content ha
AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04).
Data as JSON: /data/errors/71b09d39c56c9477.json.
Report an issue: GitHub.