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

  1. 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.
  2. 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.
  3. If the call site actually wanted to set a single key, use params = params.set('key', value) instead of update.
  4. 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

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


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/ed5014756ac3662d.json. Report an issue: GitHub.