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
Warpway replay
Flags it, given only what existed before merge
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
- Fixed in
- 2.34.2 (#7441)
- Language
- Python
What Warpway flags in the replay
- CorrectnessIssue found
- SecurityIncomplete
- Testing / ReliabilityIssue found
- Architecture / MaintainabilityIssue found
- Backward CompatibilityIssue found
- PerformanceCleared
- Database SafetyCleared
- 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 torequests.get,Session.request, orRequest. The new sharedMutableMapping[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. - LowBackward Compatibilitysrc/requests/models.py:312
Request.headers type no longer permits a supported None reset
Request.headerswas previously annotated as optional. Existing code can reset an already constructed request withreq.headers = Noneand then callreq.prepare():PreparedRequest.prepare_headersexplicitly accepts None and creates an empty header dictionary. The new non-optionalMutableMappingattribute 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
HeadersType: TypeAlias = MutableMapping[str, str | bytes] | NoneHow 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.
import requests
headers = {"Authorization": "Bearer token"} # inferred as dict[str, str]
requests.get("https://example.com", headers=headers)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:
headers: dict[str, str | bytes] = {"Authorization": "Bearer token"}What happened next
Opened
#7431 opened
Merged
Release
Requests 2.34.1 released
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
- 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.
- 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.
- 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.
More case studies
- psf/requests #6667Python
requests 2.32 SSLContext regression: the wrong mTLS client certificate
- Warpway flags in a replay
- Client certificates leaking across requests through one shared SSLContext
- What happened
- Reported by users from 7 days after merge; reverted a year later in 2.32.5
- honojs/hono #5366TypeScript
Hono formData() after text() silently corrupts binary file uploads
- Warpway flags in a replay
- Binary file uploads silently altered when text() is read first
- What happened
- Not previously reported; we filed it as honojs/hono#5539
- honojs/hono #5133TypeScript
Hono cloneRawRequest: stale Content-Length after multipart re-encoding
- Warpway flags in a replay
- Forwarded multipart clones keep a Content-Length that no longer matches the body
- What happened
- Found independently 36 days after merge; fixed in 4.13.4