Skip to content
psf/requests #7431Python

requests 2.34.1 mypy error: headers dict[str, str] has incompatible type

Requests 2.34.1 typed headers as MutableMapping[str, str | bytes], so mypy rejected dict[str, str]. A Warpway replay of the PR flags it; 2.34.2 reverted it.

Published 2 min readReplayed through Warpway on By the Warpway team

Replay vs. what happened

  1. Warpway replay

    Flags it, given only what existed before merge

  2. Partial revert opened (#7441)

    Same day as merge

Flagged: Header input type rejects ordinary dict[str, str] under type checkers

Replayed on 2026-10-07. Warpway was not installed on psf/requests when this pull request was open.

Requests ships type annotations, so its typing is part of its public API: a change can break type checking for downstream code even when the runtime behaviour is unchanged. In May 2026, psf/requests#7431 tightened the type of request headers from Mapping[str, str | bytes] to MutableMapping[str, str | bytes], so that code mutating Request.headers would type-check. It shipped in Requests 2.34.1 minutes after merge.

On 2026-10-07 we replayed the pull request through Warpway as it stood before merge. The Correctness lens reports that the new type rejects the most common way people pass headers: a plain dict[str, str]. The maintainers reached the same conclusion two hours after release, and 2.34.2 partially reverted it the next day.

Key facts

Pull request
psf/requests#7431
Merged
2026-05-13
Shipped in
2.34.1
Affected
2.34.1
Language
Python

What Warpway flags in the replay

Warpway reviewReplayed 2026-10-07#7431 Fix mutability issues with headers input types
  • CorrectnessIssue found
  • SecurityIncomplete
  • Testing / ReliabilityIssue found
  • Architecture / MaintainabilityIssue found
  • Backward CompatibilityIssue found
  • PerformanceCleared
  • Database SafetyCleared
  1. MediumCorrectnesssrc/requests/_types.py:112

    Header input type rejects ordinary string-valued dictionaries

    The previous header input contract accepted Mapping[str, str | bytes], so a variable annotated dict[str, str] could be passed to requests.get, Session.request, or Request. The new shared MutableMapping[str, str | bytes] is invariant in its value type, so static type checkers reject that same ordinary string-only dictionary as an argument, even though the runtime still accepts it and the existing request test uses this form. This is a public typing regression in the very path the PR aims to improve; read-only mappings are also newly excluded despite being consumed only as mappings by the preparation path.
  2. LowBackward Compatibilitysrc/requests/models.py:312

    Request.headers type no longer permits a supported None reset

    Request.headers was previously annotated as optional. Existing code can reset an already constructed request with req.headers = None and then call req.prepare(): PreparedRequest.prepare_headers explicitly accepts None and creates an empty header dictionary. The new non-optional MutableMapping attribute annotation rejects that valid sequence in statically checked clients.

Warpway saw the pull request as it stood before merge: final code, title, description and commits, without review comments or later history. Findings are quoted verbatim from the recorded review. Several lenses can report the same issue; Warpway merges them, and the first paragraph is shown.

The change

src/requests/_types.py (2.34.1)
HeadersType: TypeAlias = MutableMapping[str, str | bytes] | None

How the bug works

Mapping is read-only, so it is covariant in its value type: a dict[str, str] is a valid Mapping[str, str | bytes], because reading a str where str | bytes is expected is always safe. MutableMapping allows writes, so it is invariant: a function that accepts MutableMapping[str, str | bytes] may store bytes into it, which would break a dictionary declared to hold only str. Type checkers therefore reject the call, even though Requests never writes to it and the code runs fine.

python
import requests

headers = {"Authorization": "Bearer token"}  # inferred as dict[str, str]
requests.get("https://example.com", headers=headers)
text
error: Argument "headers" to "get" has incompatible type "dict[str, str]";
expected "MutableMapping[str, str | bytes] | None"  [arg-type]

We reproduced this with mypy --strict on Requests 2.34.1. The same file passes on 2.34.0 and 2.34.2.

Are you affected?

Only if you run a type checker over code that uses Requests 2.34.1. Runtime behaviour did not change.

Fix or workaround

Upgrade to Requests 2.34.2, which moves the type back to Mapping. If you are pinned to 2.34.1, widen your annotation, which we verified passes:

python
headers: dict[str, str | bytes] = {"Authorization": "Bearer token"}

What happened next

  1. Opened

    #7431 opened

  2. Release

    Requests 2.34.1 released

  3. Warpway replay

    Warpway replay: given the pull request as it stood before merge, it reports that dict[str, str] is rejected

Frequently asked questions

What is the exact mypy error?

Argument "headers" to "get" has incompatible type "dict[str, str]"; expected "MutableMapping[str, str | bytes] | None" [arg-type]. We reproduced it with requests 2.34.1 and mypy --strict; 2.34.0 and 2.34.2 pass.

Why is dict[str, str] not a MutableMapping[str, str | bytes]?

MutableMapping is invariant in its value type: a function that accepts MutableMapping[str, str | bytes] is allowed to write bytes values into it, which would break a dict declared to hold only str. Mapping is read-only and covariant, so dict[str, str] is accepted there.

How do I fix it?

Upgrade to requests 2.34.2. On 2.34.1, annotate the dict as dict[str, str | bytes] or pin requests to 2.34.0.

How we ran this

  1. 01

    Replay the pull request as it stood before merge

    Its final code, title, description and commits, and the repository at the fork point. No review comments, no bug reports, nothing after merge.

  2. 02

    Review with Warpway’s default lenses

    The production role models, lenses and default policy, run through Codex subscription access. One run per pull request, with no repository configuration.

  3. 03

    Compare with what actually happened

    Later fixes, reverts and bug reports in the public history, linked from every page so you can check our reading.

This pull request is one of 12 we replayed on 2026-10-07 from hono, axios and requests, including ones that were never fixed or reverted and the cases Warpway got wrong. Warpway was not installed on these repositories at the time; every review here is a replay. Older pull requests may appear in the model's training data, which is one reason we include recent ones and a bug nobody had reported. See every result from the pilot.

These replays used Codex subscription access rather than the production OpenAI API. They establish the recorded findings, not production API cost, general accuracy, customer time savings or an approval rate. The projects are open-source examples, not Warpway customers.