Browse Source

Add nested forward-ref regression coverage

pull/15300/head
itsviseph 3 months ago
parent
commit
a7e6ed35c7
  1. 6
      fastapi/dependencies/utils.py
  2. 41
      tests/test_stringified_annotations_forwardrefs.py

6
fastapi/dependencies/utils.py

@ -1,3 +1,4 @@
import builtins
import dataclasses
import inspect
import sys
@ -84,6 +85,8 @@ multipart_not_installed_error = (
class _ForwardRefNamespace(dict[str, Any]):
def __missing__(self, key: str) -> Any:
if hasattr(builtins, key):
return getattr(builtins, key)
value = ForwardRef(key)
self[key] = value
return value
@ -253,6 +256,9 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
if isinstance(annotation, str):
try:
# These annotation strings come from Python's own annotation machinery,
# not directly from user input, so evaluating them here matches the
# standard runtime resolution path.
annotation = eval(annotation, globalns, globalns)
except NameError:
# Preserve the outer typing structure (e.g. Annotated[..., Depends(...)])

41
tests/test_stringified_annotations_forwardrefs.py

@ -77,3 +77,44 @@ async def read_root(potato: Annotated["Potato", Depends(get_potato)]):
operation = client.get("/openapi.json").json()["paths"]["/"]["get"]
assert "parameters" not in operation
@needs_py310
def test_nested_forwardref_inside_annotated_dependency_preserves_structure():
app = _build_app("""
from __future__ import annotations
from dataclasses import dataclass
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_potatoes() -> list[Potato]:
return [Potato(color="red", size=10), Potato(color="gold", size=7)]
@app.get("/")
async def read_root(potatoes: Annotated[list[Potato], Depends(get_potatoes)]):
return {
"items": [{"color": potato.color, "size": potato.size} for potato in potatoes]
}
@dataclass
class Potato:
color: str
size: int
""")
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200, response.text
assert response.json() == {
"items": [
{"color": "red", "size": 10},
{"color": "gold", "size": 7},
]
}
operation = client.get("/openapi.json").json()["paths"]["/"]["get"]
assert "parameters" not in operation

Loading…
Cancel
Save