From 4d445a443623890bed82ed1224ef71a226062895 Mon Sep 17 00:00:00 2001 From: Ahnjae Shin Date: Tue, 4 Jun 2024 08:15:14 +0900 Subject: [PATCH 1/5] Use globals of original function --- fast_depends/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fast_depends/utils.py b/fast_depends/utils.py index 7ae51d4e..0fce4e6c 100644 --- a/fast_depends/utils.py +++ b/fast_depends/utils.py @@ -1,6 +1,7 @@ import asyncio import functools import inspect +import sys from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager from typing import ( TYPE_CHECKING, @@ -80,7 +81,9 @@ def get_typed_signature(call: Callable[..., Any]) -> Tuple[inspect.Signature, An locals = collect_outer_stack_locals() - globalns = getattr(call, "__globals__", {}) + # If call is wrapped, __globals__ point to the wrapper's globals + # We need to get the globals of the wrapped function + globalns = sys.modules.get(call.__module__).__dict__ typed_params = [ inspect.Parameter( name=param.name, From 81506b5c826f88fc18b39b7bb8af21af7a492f5d Mon Sep 17 00:00:00 2001 From: Ahnjae Shin Date: Wed, 5 Jun 2024 08:53:48 +0900 Subject: [PATCH 2/5] Add test and fix impl. --- fast_depends/utils.py | 18 +++++++++--------- tests/__init__.py | 0 tests/test_prebuild.py | 25 +++++++++++++++++++++++++ tests/wrapper.py | 11 +++++++++++ 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/wrapper.py diff --git a/fast_depends/utils.py b/fast_depends/utils.py index 0fce4e6c..7614480a 100644 --- a/fast_depends/utils.py +++ b/fast_depends/utils.py @@ -1,7 +1,6 @@ import asyncio import functools import inspect -import sys from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager from typing import ( TYPE_CHECKING, @@ -81,9 +80,11 @@ def get_typed_signature(call: Callable[..., Any]) -> Tuple[inspect.Signature, An locals = collect_outer_stack_locals() - # If call is wrapped, __globals__ point to the wrapper's globals - # We need to get the globals of the wrapped function - globalns = sys.modules.get(call.__module__).__dict__ + # We unwrap call to get the original unwrapped function + while hasattr(call, "__wrapped__"): + call = call.__wrapped__ + + globalns = getattr(call, "__globals__", {}) typed_params = [ inspect.Parameter( name=param.name, @@ -132,12 +133,11 @@ def get_typed_annotation( if isinstance(annotation, ForwardRef): annotation = evaluate_forwardref(annotation, globalns, locals) - if ( - get_origin(annotation) is Annotated - and (args := get_args(annotation)) - ): + if get_origin(annotation) is Annotated and (args := get_args(annotation)): solved_args = [get_typed_annotation(x, globalns, locals) for x in args] - annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple(solved_args[1:]) + annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple( + solved_args[1:] + ) return annotation diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_prebuild.py b/tests/test_prebuild.py index c6e01775..f1f4bc19 100644 --- a/tests/test_prebuild.py +++ b/tests/test_prebuild.py @@ -1,11 +1,36 @@ +from __future__ import annotations + from fast_depends.core import build_call_model from fast_depends.use import inject +from .wrapper import noop_wrap + +from pydantic import BaseModel + + +class Model(BaseModel): + a: str + def base_func(a: int) -> str: return "success" +def model_func(m: Model) -> str: + return m.a + + def test_prebuild(): model = build_call_model(base_func) inject()(None, model)(1) + + +def test_prebuild_with_wrapper(): + # build_call_model should work even if function is wrapped with a + # wrapper that is imported from different module + func = noop_wrap(model_func) + call_model = build_call_model(func) + + assert call_model.model + # Fails if function unwrapping is not done at type introspection + call_model.model.model_rebuild() diff --git a/tests/wrapper.py b/tests/wrapper.py new file mode 100644 index 00000000..0f83b577 --- /dev/null +++ b/tests/wrapper.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from functools import wraps + + +def noop_wrap(func): + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper From bfc44dd2d67229dd24456f366cae80d8c735711a Mon Sep 17 00:00:00 2001 From: Ahnjae Shin Date: Wed, 5 Jun 2024 08:55:07 +0900 Subject: [PATCH 3/5] inc. coverage --- tests/test_prebuild.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_prebuild.py b/tests/test_prebuild.py index f1f4bc19..f35cafd5 100644 --- a/tests/test_prebuild.py +++ b/tests/test_prebuild.py @@ -26,9 +26,11 @@ def test_prebuild(): def test_prebuild_with_wrapper(): + func = noop_wrap(model_func) + assert func(Model(a="Hi!")) == "Hi!" + # build_call_model should work even if function is wrapped with a # wrapper that is imported from different module - func = noop_wrap(model_func) call_model = build_call_model(func) assert call_model.model From d9df4eb0587afb461e41863430c01eb1c890baf9 Mon Sep 17 00:00:00 2001 From: Ahnjae Shin Date: Wed, 5 Jun 2024 08:57:08 +0900 Subject: [PATCH 4/5] revert styling --- fast_depends/utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fast_depends/utils.py b/fast_depends/utils.py index 7614480a..e3cdeb4a 100644 --- a/fast_depends/utils.py +++ b/fast_depends/utils.py @@ -133,11 +133,12 @@ def get_typed_annotation( if isinstance(annotation, ForwardRef): annotation = evaluate_forwardref(annotation, globalns, locals) - if get_origin(annotation) is Annotated and (args := get_args(annotation)): + if ( + get_origin(annotation) is Annotated + and (args := get_args(annotation)) + ): solved_args = [get_typed_annotation(x, globalns, locals) for x in args] - annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple( - solved_args[1:] - ) + annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple(solved_args[1:]) return annotation From 55727040f2b961c2a38142d1387a78835fc8b61f Mon Sep 17 00:00:00 2001 From: Ahnjae Shin Date: Wed, 5 Jun 2024 16:14:17 +0900 Subject: [PATCH 5/5] Use unwrap, Pass test for pydantic v1 --- fast_depends/utils.py | 12 +++++------- tests/test_prebuild.py | 7 ++++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/fast_depends/utils.py b/fast_depends/utils.py index e3cdeb4a..1c914d71 100644 --- a/fast_depends/utils.py +++ b/fast_depends/utils.py @@ -81,8 +81,7 @@ def get_typed_signature(call: Callable[..., Any]) -> Tuple[inspect.Signature, An locals = collect_outer_stack_locals() # We unwrap call to get the original unwrapped function - while hasattr(call, "__wrapped__"): - call = call.__wrapped__ + call = inspect.unwrap(call) globalns = getattr(call, "__globals__", {}) typed_params = [ @@ -133,12 +132,11 @@ def get_typed_annotation( if isinstance(annotation, ForwardRef): annotation = evaluate_forwardref(annotation, globalns, locals) - if ( - get_origin(annotation) is Annotated - and (args := get_args(annotation)) - ): + if get_origin(annotation) is Annotated and (args := get_args(annotation)): solved_args = [get_typed_annotation(x, globalns, locals) for x in args] - annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple(solved_args[1:]) + annotation.__origin__, annotation.__metadata__ = solved_args[0], tuple( + solved_args[1:] + ) return annotation diff --git a/tests/test_prebuild.py b/tests/test_prebuild.py index f35cafd5..368b54fe 100644 --- a/tests/test_prebuild.py +++ b/tests/test_prebuild.py @@ -35,4 +35,9 @@ def test_prebuild_with_wrapper(): assert call_model.model # Fails if function unwrapping is not done at type introspection - call_model.model.model_rebuild() + + if hasattr(call_model.model, "model_rebuild"): + call_model.model.model_rebuild() + else: + # pydantic v1 + call_model.model.update_forward_refs()