Skip to content

Preserve Annotated dependency metadata for stringified forward refs#3

Open
yashwant86 wants to merge 3 commits intomasterfrom
pr-15300
Open

Preserve Annotated dependency metadata for stringified forward refs#3
yashwant86 wants to merge 3 commits intomasterfrom
pr-15300

Conversation

@yashwant86
Copy link
Copy Markdown

@yashwant86 yashwant86 commented Apr 7, 2026

Mirror of fastapi#15300


Summary by MergeMonkey

  • Fixes & Patches:
    • Stringified forward references inside Annotated dependencies now preserve their metadata instead of being incorrectly treated as query parameters

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Changes

Files Summary
Forward reference resolution with metadata preservation
fastapi/dependencies/utils.py
Replaces ForwardRef+evaluate_forwardref with a two-pass eval strategy: first tries direct eval against the module globals, then falls back to a custom _ForwardRefNamespace dict that auto-creates ForwardRef objects for unknown names, preserving Annotated wrapper metadata (e.g. Depends) even when inner types are not yet defined.
Tests for stringified forward reference handling
tests/test_stringified_annotations_forwardrefs.py
Adds three test cases covering late-defined types, explicitly stringified types, and nested generics inside Annotated dependencies with forward references, verifying metadata preservation and correct OpenAPI schema generation.

Sequence Diagram

sequenceDiagram
    participant Client
    participant FastAPI
    participant DepUtils as dependencies/utils
    participant FRNamespace as _ForwardRefNamespace

    Client->>FastAPI: GET /
    FastAPI->>DepUtils: get_typed_annotation("Annotated[Potato, Depends(...)]", globalns)
    DepUtils->>DepUtils: isinstance(annotation, str) → True
    DepUtils->>DepUtils: eval(annotation, globalns, globalns)
    alt NameError (Potato not defined yet)
        DepUtils->>FRNamespace: create _ForwardRefNamespace(globalns)
        FRNamespace->>FRNamespace: __missing__("Potato") → ForwardRef("Potato")
        DepUtils->>DepUtils: eval(annotation, forwardref_globalns, forwardref_globalns)
        Note over DepUtils: Returns Annotated[ForwardRef('Potato'), Depends(...)]
    else Success
        Note over DepUtils: Returns Annotated[Potato, Depends(...)]
    end
    DepUtils-->>FastAPI: Annotated type with Depends metadata preserved
    FastAPI->>FastAPI: Resolve dependency via Depends(get_potato)
    FastAPI-->>Client: 200 {"color": "red", "size": 10}
Loading

Dig Deeper With Commands

  • /review <file-path> <function-optional>
  • /chat <file-path> "<question>"
  • /roast <file-path>

Runs only when explicitly triggered.

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Actionable Comments Posted: 0

👀 Worth a Look (1)
[MEDIUM] Only NameError is caught — other eval failures (TypeError, AttributeError) will propagate without context - fastapi/dependencies/utils.py (258, 263)
The old code used pydantic's `eval_type_lenient` which likely handled multiple exception types gracefully. The new `eval()` call only catches `NameError`, so a stringified annotation like `"MyClass[int]"` where `MyClass` doesn't support `__class_getitem__` would raise an unhandled `TypeError`. Consider catching a broader set of exceptions (e.g., `except (NameError, TypeError):`) to match the previous lenient behavior.

Catch `(NameError, TypeError)` or even `Exception` in the first eval try/except to preserve the lenient behavior of the old code path.
🧾 Coverage Summary
✔️ Covered (2 files)
- fastapi/dependencies/utils.py
- tests/test_stringified_annotations_forwardrefs.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants