def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
signature = inspect.signature(call)
- globalns = getattr(call, "__globals__", {})
+ unwrapped = inspect.unwrap(call)
+ globalns = getattr(unwrapped, "__globals__", {})
typed_params = [
inspect.Parameter(
name=param.name,
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
signature = inspect.signature(call)
+ unwrapped = inspect.unwrap(call)
annotation = signature.return_annotation
if annotation is inspect.Signature.empty:
return None
- globalns = getattr(call, "__globals__", {})
+ globalns = getattr(unwrapped, "__globals__", {})
return get_typed_annotation(annotation, globalns)
--- /dev/null
+from pydantic import BaseModel
+
+
+def forwardref_method(input: "ForwardRefModel") -> "ForwardRefModel":
+ return ForwardRefModel(x=input.x + 1)
+
+
+class ForwardRefModel(BaseModel):
+ x: int = 0
--- /dev/null
+import functools
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+from .forward_reference_type import forwardref_method
+
+
+def passthrough(f):
+ @functools.wraps(f)
+ def method(*args, **kwargs):
+ return f(*args, **kwargs)
+
+ return method
+
+
+def test_wrapped_method_type_inference():
+ """
+ Regression test ensuring that when a method imported from another module
+ is decorated with something that sets the __wrapped__ attribute (functools.wraps),
+ then the types are still processed correctly, including dereferencing of forward
+ references.
+ """
+ app = FastAPI()
+ client = TestClient(app)
+ app.post("/endpoint")(passthrough(forwardref_method))
+ app.post("/endpoint2")(passthrough(passthrough(forwardref_method)))
+ with client:
+ response = client.post("/endpoint", json={"input": {"x": 0}})
+ response2 = client.post("/endpoint2", json={"input": {"x": 0}})
+ assert response.json() == response2.json() == {"x": 1}