Fix Annotated forward ref resolution with from __future__ import annotations#15519
Open
MD-Mushfiqur123 wants to merge 9 commits into
Open
Fix Annotated forward ref resolution with from __future__ import annotations#15519MD-Mushfiqur123 wants to merge 9 commits into
MD-Mushfiqur123 wants to merge 9 commits into
Conversation
…tations When using rom __future__ import annotations, annotations are stored as strings. If Annotated[SomeClass, Depends()] references a class defined after the route decorator, the forward reference could not be resolved at decoration time. FastAPI's evaluate_forwardref() does not raise on unresolvable references - it returns the raw ForwardRef object. The existing code only handled the string-to-ForwardRef conversion but did not check whether resolution actually succeeded, allowing an unresolvable ForwardRef to leak into the type annotation. This caused Pydantic to fail with 'class-not-fully-defined' errors and the Depends metadata to be lost. The fix adds a fallback: when evaluate_forwardref() returns a ForwardRef (rather than the resolved type) and the annotation string is Annotated- shaped, a lenient resolution evaluates the annotation with a namespace that maps undefined names to Any. This preserves the Annotated structure so FastAPI can extract the Depends metadata.
In Python 3.13+, ForwardRef._evaluate() requires 'recursive_guard' as a keyword-only argument. Pass it as keyword to support both older and newer Python versions.
122db99 to
63613e0
Compare
Python 3.14 added a new 'type_params' parameter to ForwardRef._evaluate() (PEP 695). Failing to pass it triggers a DeprecationWarning, which is treated as error by FastAPI's test suite (-W error). Pass type_params=() on Python 3.14+ to silence the warning.
19693f9 to
afd043b
Compare
0815346 to
e20b249
Compare
1ae49d4 to
477ebee
Compare
- Remove duplicate _resolve_forward_ref_lenient function - Use ForwardRef.evaluate() instead of deprecated _evaluate() - Use frozenset() instead of set() for recursive_guard parameter - Add noqa for E402 in test file (deliberate late import)
…n warning - Keep ForwardRef._evaluate (recognized by mypy/ty typeshed) - Fix class _LenientNamespace(dict) -> _LenientNamespace(dict[str, Any]) - Use frozenset() instead of set() for recursive_guard - Ignore ForwardRef._evaluate deprecation warning in pytest config
Contributor
📝 Docs previewLast commit 375b767 at: https://7238a4d2.fastapitiangolo.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #13056
When using
from __future__ import annotations, annotations are stored as strings at compile time. IfAnnotated[SomeClass, Depends()]references a class defined after the route decorator, the forward reference could not be resolved at decoration time.Root Cause
FastAPI's
evaluate_forwardref()(wrapping Pydantic'stry_eval_type) does not raise when a ForwardRef can't be resolved — it returns the raw ForwardRef object withsuccess=False. The existing code only handled the string-to-ForwardRef conversion but never checked whether the resolution actually succeeded, allowing an unresolvable ForwardRef to leak into the type annotation. This caused Pydantic's TypeAdapter to fail withclass-not-fully-definedand theDependsmetadata to be lost.Fix
When
evaluate_forwardref()returns a ForwardRef (rather than the resolved type) and the annotation string containsAnnotated[, a lenient fallback evaluates the annotation with a namespace that maps undefined names toAny. This preserves theAnnotatedstructure soanalyze_paramcan extract theDependsmetadata.Tests
test_forward_ref_annotated_depends— Validates thatAnnotated[Potato, Depends(get_potato)]works whenPotatois defined after the route underfrom __future__ import annotationstest_forward_ref_annotated_depends_openapi— Validates that the OpenAPI schema doesn't expose the dependency as a query parameterAll existing 96+ dependency tests continue to pass.