Browse Source

fix: resolve ForwardRef in Annotated types for OpenAPI schema generation

When using `from __future__ import annotations` with `Annotated[Type, Depends(...)]`,
ForwardRefs that couldn't be resolved at route registration time are now properly
resolved when generating the OpenAPI schema.

Fixes #13056
pull/15131/head
BillionClaw 4 months ago
parent
commit
dc28d640db
  1. 29
      fastapi/_compat/v2.py
  2. 50
      tests/test_annotated_forward_ref.py

29
fastapi/_compat/v2.py

@ -152,6 +152,31 @@ class ModelField:
config=self.config, config=self.config,
) )
@property
def core_schema(self) -> CoreSchema:
"""Get the core schema, rebuilding if necessary to resolve ForwardRefs."""
schema = self._type_adapter.core_schema
# Check if this is a mock schema (unresolved ForwardRef)
if hasattr(schema, '_error_message'):
# Try to rebuild the type adapter to resolve ForwardRefs
# Search all loaded modules for the types we need
import sys
# Build a combined namespace from all loaded modules
combined_globals: dict[str, Any] = {}
for module in sys.modules.values():
if module is None:
continue
try:
module_dict = getattr(module, "__dict__", {})
combined_globals.update(module_dict)
except Exception:
continue
self._type_adapter.rebuild(force=True, _types_namespace=combined_globals)
schema = self._type_adapter.core_schema
return schema
def get_default(self) -> Any: def get_default(self) -> Any:
if self.field_info.is_required(): if self.field_info.is_required():
return Undefined return Undefined
@ -232,7 +257,7 @@ class ModelField:
def _has_computed_fields(field: ModelField) -> bool: def _has_computed_fields(field: ModelField) -> bool:
computed_fields = field._type_adapter.core_schema.get("schema", {}).get( computed_fields = field.core_schema.get("schema", {}).get(
"computed_fields", [] "computed_fields", []
) )
return len(computed_fields) > 0 return len(computed_fields) > 0
@ -316,7 +341,7 @@ def get_definitions(
if (separate_input_output_schemas or _has_computed_fields(field)) if (separate_input_output_schemas or _has_computed_fields(field))
else "validation" else "validation"
), ),
field._type_adapter.core_schema, field.core_schema,
) )
for field in list(fields) + list(unique_flat_model_fields) for field in list(fields) + list(unique_flat_model_fields)
] ]

50
tests/test_annotated_forward_ref.py

@ -0,0 +1,50 @@
"""Test for ForwardRef resolution with Annotated and Depends.
This test verifies the fix for: https://github.com/fastapi/fastapi/issues/13056
When using `from __future__ import annotations`, ForwardRefs in Annotated types
should be properly resolved when generating OpenAPI schema.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Annotated
import json
from fastapi import Depends, FastAPI
from fastapi.openapi.utils import get_openapi
def test_annotated_with_forward_ref_openapi():
"""Test that Annotated with ForwardRef generates correct OpenAPI schema.
This test reproduces the issue where Potato is defined AFTER the route,
which causes it to be a ForwardRef when the route is processed.
"""
app = FastAPI()
def get_potato() -> Potato:
return Potato(color='red', size=10)
@app.get('/')
async def read_root(potato: Annotated[Potato, Depends(get_potato)]):
return {'Hello': 'World'}
# Define Potato AFTER the route - this is the problematic case
@dataclass
class Potato:
color: str
size: int
# Generate OpenAPI schema - this should not raise an error
openapi_schema = get_openapi(title="Test", version="1.0.0", routes=app.routes)
print(f"Schema keys: {openapi_schema.keys()}")
if "components" in openapi_schema:
print(json.dumps(openapi_schema["components"], indent=2))
else:
print("No components in schema")
if __name__ == "__main__":
test_annotated_with_forward_ref_openapi()
print("Test completed!")
Loading…
Cancel
Save