Skip to content

Fix Annotated ForwardRef dependencies with postponed annotations#5

Open
yashwant86 wants to merge 5 commits intomasterfrom
pr-15234
Open

Fix Annotated ForwardRef dependencies with postponed annotations#5
yashwant86 wants to merge 5 commits intomasterfrom
pr-15234

Conversation

@yashwant86
Copy link
Copy Markdown

@yashwant86 yashwant86 commented Apr 7, 2026

Mirror of fastapi#15234


Summary by MergeMonkey

  • Squashed Bugs:
    • Resolves annotated forward-reference dependencies when using postponed annotations (PEP 563)

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 10, 2026

Changes

Files Summary
Lenient ForwardRef Resolution Fallback
fastapi/dependencies/utils.py
Introduces a fallback mechanism for resolving ForwardRef annotations that survive initial evaluate_forwardref. Uses a _LenientTypeResolutionDict that returns builtins or wraps unknown names as ForwardRef, then eval's the forward arg string. Also restructures the annotation check so ForwardRef handling applies regardless of whether the annotation started as a string.
Unit Tests for ForwardRef Resolution
tests/test_dependencies_utils.py
Adds a unit test verifying that get_typed_annotation falls back to lenient ForwardRef resolution for complex annotated strings containing unknown types.
Integration Test for Postponed Annotations
tests/test_stringified_annotation_forwardref_dependency.py
Adds end-to-end test using `from __future__ import annotations` with a forward-referenced dataclass dependency, verifying both endpoint response and OpenAPI schema generation.

Sequence Diagram

sequenceDiagram
    participant Client
    participant FastAPI
    participant DepUtils as DependencyUtils
    participant LenientDict as _LenientTypeResolutionDict

    Client->>FastAPI: GET /
    FastAPI->>DepUtils: get_typed_annotation("Annotated[Potato, Depends(get_potato)]", globalns)
    DepUtils->>DepUtils: Convert string to ForwardRef
    DepUtils->>DepUtils: evaluate_forwardref(ForwardRef, globalns, globalns)
    alt ForwardRef resolved
        DepUtils-->>FastAPI: Return resolved annotation
    else ForwardRef still unresolved
        DepUtils->>LenientDict: Create with globalns
        DepUtils->>DepUtils: eval(forward_arg, LenientDict)
        LenientDict->>LenientDict: __missing__("Potato") → ForwardRef("Potato")
        DepUtils-->>FastAPI: Return Annotated[ForwardRef("Potato"), Depends(...)]
    end
    FastAPI->>FastAPI: Resolve dependency get_potato()
    FastAPI-->>Client: {"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.

@yashwant86
Copy link
Copy Markdown
Author

/roast fastapi/dependencies/utils.py

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 10, 2026

/roast fastapi/dependencies/utils.py

Eval-ing user ForwardRefs with builtins fallback and bare except pass? That's not lenient, that's "lmao if it executes my shell command"! 💣😂

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 10, 2026

Actionable Comments Posted: 0

🧹 Nitpick comments (1)
Broad \`except Exception: pass\` silently swallows all eval failures - fastapi/dependencies/utils.py (258, 263)
The rest of the codebase uses specific exception types \(e.g., \`except NameError:\` at line 217\). This bare \`except Exception: pass\` will silently swallow unexpected errors like \`TypeError\` or \`AttributeError\` during eval, making debugging harder. Consider narrowing to \`except \(NameError, SyntaxError\):\` or at minimum logging at debug level.

Narrow the catch to specific expected exceptions like \`except \(NameError, SyntaxError, TypeError\):\` or add a \`logger.debug\(\)\` call to aid debugging.
🧾 Coverage Summary
✔️ Covered (3 files)
- fastapi/dependencies/utils.py
- tests/test_dependencies_utils.py
- tests/test_stringified_annotation_forwardref_dependency.py

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Actionable Comments Posted: 0

👀 Worth a Look (1)
[MEDIUM] Bare `except Exception: pass` silently swallows eval failures - fastapi/dependencies/utils.py (262, 263)
If the fallback `eval()` fails for any reason (syntax error in the annotation string, unexpected type resolution, etc.), the exception is silently discarded and the annotation remains as an unresolved ForwardRef. This could make it very difficult to debug annotation resolution issues — consider at least logging a debug-level message so developers have some signal when the fallback path fails.

Add a `logger.debug(...)` or `logger.warning(...)` in the except block so failures are at least visible when debugging: `except Exception as e: logger.debug(f'Failed to evaluate forward ref {annotation.__forward_arg__!r}: {e}')`.
🧾 Coverage Summary
✔️ Covered (3 files)
- fastapi/dependencies/utils.py
- tests/test_dependencies_utils.py
- tests/test_stringified_annotation_forwardref_dependency.py

@yashwant86
Copy link
Copy Markdown
Author

/review --force

annotation = eval( # noqa: S307
annotation.__forward_arg__, _LenientTypeResolutionDict(globalns)
)
except Exception:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare except Exception: pass silently swallows all eval errors

The fallback eval() catches and discards all exceptions including SyntaxError, NameError, and TypeError. When a developer has a genuine typo or syntax error in a complex Annotated type annotation, this will silently degrade to an unresolved ForwardRef with no diagnostic output, making the root cause very hard to track down. Consider logging a debug/warning message or narrowing the exception type.

Add a logger.debug() or warnings.warn() inside the except block so developers can diagnose annotation resolution failures, e.g. except Exception as e: logger.debug(f'Failed to resolve ForwardRef {annotation.__forward_arg__!r}: {e}').

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Actionable Comments Posted: 1

🧾 Coverage Summary
✔️ Covered (3 files)
- fastapi/dependencies/utils.py
- tests/test_dependencies_utils.py
- tests/test_stringified_annotation_forwardref_dependency.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