encode/httpx · error · RuntimeError
QueryParams are immutable since 0.18.0. Use `q = q.merge(...
Error message
QueryParams are immutable since 0.18.0. Use `q = q.merge(...)` to create an updated copy.
What it means
Since httpx 0.18.0, QueryParams is an immutable type. The legacy QueryParams.update(params) method is kept only to surface a clear migration error instead of silently no-op'ing or mutating in place. Calling it raises RuntimeError directing you to the functional replacement: q = q.merge(...), which returns a new QueryParams with the merged key/value pairs (last-wins on conflict).
Source
Thrown at httpx/_urls.py:632
def __hash__(self) -> int:
return hash(str(self))
def __eq__(self, other: typing.Any) -> bool:
if not isinstance(other, self.__class__):
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.update(new) with params = params.merge(new); merge takes a dict, list of pairs, query string, or another QueryParams and returns a new immutable instance.
- If you were updating many keys, chain or build a single dict and merge once: params = params.merge({'a': '1', 'b': '2'}) to avoid repeated allocations.
- If the call site actually wanted to set a single key, use params = params.set('key', value) instead of update.
- Search the dependency tree (grep -r '.update(' on httpx.QueryParams / request kwargs) for SDKs pinning old httpx; bump or patch them, since their internal update() call is the real source.
Example fix
// before
q = httpx.QueryParams('a=1')
q.update({'b': '2'}) # raises RuntimeError
// after
q = httpx.QueryParams('a=1')
q = q.merge({'b': '2'}) # new immutable QueryParams('a=1&b=2') Defensive patterns
Strategy: validation
Validate before calling
import httpx
def safe_update(q, new):
"""Merge new params into q regardless of httpx version quirks.
Works for immutable (>=0.18.0) QueryParams by always returning a new
instance via merge; never calls the legacy update().
"""
if not isinstance(q, httpx.QueryParams):
q = httpx.QueryParams(q)
return q.merge(new) Type guard
import httpx
def is_immutable_queryparams(q) -> bool:
"""True when q is a >=0.18.0 QueryParams (update raises by design)."""
return isinstance(q, httpx.QueryParams) Try / catch
import httpx
def merge_or_fallback(q, new):
try:
return q.merge(new)
except (RuntimeError, AttributeError):
# Very old httpx without merge(), or unexpected immutable guard:
rebuilt = dict(q.multi_items())
rebuilt.update(dict(new))
return httpx.QueryParams(rebuilt) Prevention
- Treat httpx.QueryParams as a value type: always reassign the result (params = params.merge(...)), never call update().
- Pin httpx >=0.18.0 in requirements and grep codebase for '.update(' on QueryParams to catch legacy calls before runtime.
- Add a lint rule or pre-commit grep for 'QueryParams' followed within a few lines by '.update(' or '[.*] ='.
- When wrapping httpx for a library, expose only merge/set/add/remove on your own params adapter so callers can't hit the legacy mutation API.
When it happens
Trigger: Calling QueryParams.update(...) on any instance, e.g. q = httpx.QueryParams('a=1'); q.update({'b': '2'}). Also reached indirectly by older client code or tutorials that predate 0.18.0 and treat QueryParams like a mutable dict. The raise is unconditional inside update() at httpx/_urls.py:632, so any call hits it.
Common situations: Upgrading httpx across the 0.18.0 boundary while relying on pre-upgrade tutorials, StackOverflow answers, or copy-pasted SDK wrappers that call params.update(...). Framework integrations (Starlette/TestClient older revisions) that mutated request query params in place. Anyone porting requests-style code where dict mutation was idiomatic.
Related errors
- QueryParams are immutable since 0.18.0. Use `q = q.set(key,
- 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/ed5014756ac3662d.json.
Report an issue: GitHub.