diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 081b63a8b..1ccb8d03c 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -231,12 +231,17 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]: def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: signature = inspect.signature(call) globalns = getattr(call, "__globals__", {}) + typed_params = [ inspect.Parameter( name=param.name, kind=param.kind, default=param.default, - annotation=get_typed_annotation(param.annotation, globalns), + annotation=get_typed_annotation( + param.annotation, + globalns, + collect_outer_locals(), + ), ) for param in signature.parameters.values() ] @@ -244,10 +249,30 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: return typed_signature -def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: +def collect_outer_locals() -> Dict[str, Any]: + frame = inspect.currentframe() + + locals = {} + finded = False + while frame is not None: + if finded: + locals.update(frame.f_locals) + + # Find first FastAPI outer frame + if frame.f_code.co_name == "decorator": + finded = True + + frame = frame.f_back + + return locals + + +def get_typed_annotation( + annotation: Any, globalns: Dict[str, Any], localns: Dict[str, Any] +) -> Any: if isinstance(annotation, str): annotation = ForwardRef(annotation) - annotation = evaluate_forwardref(annotation, globalns, globalns) + annotation = evaluate_forwardref(annotation, globalns, localns) return annotation @@ -259,7 +284,11 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any: return None globalns = getattr(call, "__globals__", {}) - return get_typed_annotation(annotation, globalns) + return get_typed_annotation( + annotation, + globalns, + collect_outer_locals(), + ) def get_dependant( diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py index cc567b88f..4f466e6c9 100644 --- a/tests/test_get_request_body.py +++ b/tests/test_get_request_body.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import Any + from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -105,3 +109,31 @@ def test_openapi_schema(): } }, } + + +def test_get_with_local_declared_body(): + def wrap(application: FastAPI, *args: Any): + def wrapper(func): + return application.get(*args)(func) + + return wrapper + + def init_app() -> FastAPI: + application = FastAPI() + + class LocalProduct(BaseModel): + name: str + description: str = None # type: ignore + price: float + + @wrap(application, "/product") + async def create_item(product: LocalProduct) -> LocalProduct: + return product + + return application + + client = TestClient(init_app()) + + body = {"name": "Foo", "description": "Some description", "price": 5.5} + response = client.request("GET", "/product", json=body) + assert response.json() == body