Skip to content

Commit 1c1e584

Browse files
lucaswimanuriyyotiangolo
authored
✨ Add support for wrapped functions (e.g. @functools.wraps()) used with forward references (fastapi#5077)
Co-authored-by: Yurii Karabas <1998uriyyo@gmail.com> Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
1 parent 930b27e commit 1c1e584

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

fastapi/dependencies/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
192192

193193
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
194194
signature = inspect.signature(call)
195-
globalns = getattr(call, "__globals__", {})
195+
unwrapped = inspect.unwrap(call)
196+
globalns = getattr(unwrapped, "__globals__", {})
196197
typed_params = [
197198
inspect.Parameter(
198199
name=param.name,
@@ -217,12 +218,13 @@ def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
217218

218219
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
219220
signature = inspect.signature(call)
221+
unwrapped = inspect.unwrap(call)
220222
annotation = signature.return_annotation
221223

222224
if annotation is inspect.Signature.empty:
223225
return None
224226

225-
globalns = getattr(call, "__globals__", {})
227+
globalns = getattr(unwrapped, "__globals__", {})
226228
return get_typed_annotation(annotation, globalns)
227229

228230

tests/forward_reference_type.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pydantic import BaseModel
2+
3+
4+
def forwardref_method(input: "ForwardRefModel") -> "ForwardRefModel":
5+
return ForwardRefModel(x=input.x + 1)
6+
7+
8+
class ForwardRefModel(BaseModel):
9+
x: int = 0
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import functools
2+
3+
from fastapi import FastAPI
4+
from fastapi.testclient import TestClient
5+
6+
from .forward_reference_type import forwardref_method
7+
8+
9+
def passthrough(f):
10+
@functools.wraps(f)
11+
def method(*args, **kwargs):
12+
return f(*args, **kwargs)
13+
14+
return method
15+
16+
17+
def test_wrapped_method_type_inference():
18+
"""
19+
Regression test ensuring that when a method imported from another module
20+
is decorated with something that sets the __wrapped__ attribute (functools.wraps),
21+
then the types are still processed correctly, including dereferencing of forward
22+
references.
23+
"""
24+
app = FastAPI()
25+
client = TestClient(app)
26+
app.post("/endpoint")(passthrough(forwardref_method))
27+
app.post("/endpoint2")(passthrough(passthrough(forwardref_method)))
28+
with client:
29+
response = client.post("/endpoint", json={"input": {"x": 0}})
30+
response2 = client.post("/endpoint2", json={"input": {"x": 0}})
31+
assert response.json() == response2.json() == {"x": 1}

0 commit comments

Comments
 (0)