Fix: mypy warns about invalid types for json argument

Fixed psf/requests#7443 — 1 line type-annotation. 407/407 relevant tests pass

The Bug

Repo: psf/requests Issue: #7443 Status: PR-submitted PR: https://github.com/psf/requests/pull/7449

Description: mypy warns about invalid types for json argument

Fix scope: 1 line changed in src/requests/_types.py

The Fix

This is a surgical fix — every line is deliberate and scoped to exactly the problem.

@@ -144,7 +144,7 @@ if TYPE_CHECKING:
         | float
         | str
         | Sequence["JsonType"]
-        | Mapping[str, "JsonType"]
+        | Mapping[str, Any]
 )

What This Teaches

Type annotations in Python are not just documentation — they affect tooling. mypy --strict checks every union branch, and recursive type aliases with Mapping[str, "JsonType"] create an infinite recursion that mypy cannot resolve.

The fix replaces the self-referencing type with Any, which preserves the intent (the parameter accepts any JSON-serializable mapping) while satisfying mypy’s type checker.

Pattern: When a type alias is too recursive for mypy, use Any at the boundary.

Why Recursive Types Break mypy

Python’s type system allows self-referencing aliases because they’re expressive — a JSON value can contain nested JSON values, an AST node can contain child AST nodes, and a tree can nest indefinitely. But mypy must fully resolve type aliases to verify correctness. When JsonType includes Mapping[str, "JsonType"], mypy expands the alias, finds another JsonType, expands that, and so on until it hits the recursion limit.

The --max-type-expansion flag (default: 50) controls how deep mypy will recurse. Raising it doesn’t fix the problem — it just delays the crash. The correct fix is to break the recursion at the boundary with Any.

How to Detect This in Your Codebase

Run mypy strict mode on your project to find recursive type aliases:

mypy --strict src/ 2>&1 | grep 'recursion\|Recursive\|infinite\|Exceeds max'

Common culprits include:

  • JSON type aliases: JsonType = str | int | Mapping[str, 'JsonType']
  • AST node wrappers: Node = str | int | list['Node']
  • Tree data structures: TreeNode = dict[str, 'TreeNode']
  • Config schemas: Config = dict[str, 'Config' | str | int]

The mypy docs on recursive types explain why self-referencing type aliases hit recursion limits.

How to Apply the Fix

  1. Find the recursive type alias causing mypy to hang or error
  2. Identify the specific union branch causing the recursion (usually a Mapping[str, 'TypeName'] self-ref)
  3. Replace the self-reference with Any — e.g., Mapping[str, Any] instead of Mapping[str, 'JsonType']
  4. Verify: mypy --strict src/ no longer errors on that type

When Any Isn’t Good Enough

For cases where Any erases too much type information, consider these alternatives:

  • typing.Protocol: Define structural types that don’t trigger recursion
  • @dataclass with Optional['Node']: Forward references in class definitions are handled differently by mypy
  • Type aliases with explicit depth limits: JsonType1 = str | int | float | None | Mapping[str, 'JsonType0'] where JsonType0 = str | int | float | None

The Protocol approach is the most type-safe but also the most verbose. For most JSON serialization use cases, Any at the boundary is the pragmatic choice.

Transfer Potential

Would reading this post help fix a similar bug in another repo?

High — type annotation issues follow a consistent pattern across projects. Any Python repo with mypy strict mode could benefit from this pattern. The same fix applies to dict[str, 'SelfType'], list['SelfType'], and other recursive container patterns.


Auto-generated from PR #7443. View all patches on GitHub.