Skip to content

Commit c60759d

Browse files
committed
Annotated support.
Signed-off-by: Pavel Kirilin <win10@list.ru>
1 parent 5348808 commit c60759d

3 files changed

Lines changed: 111 additions & 8 deletions

File tree

taskiq_dependencies/graph.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
try:
1010
from fastapi.params import Depends as FastapiDepends # noqa: WPS433
1111
except ImportError:
12-
FastapiDepends = None # type: ignore
12+
FastapiDepends = Dependency # type: ignore
1313

1414

1515
class DependencyGraph:
@@ -136,9 +136,8 @@ def _build_graph(self) -> None: # noqa: C901, WPS210
136136
+ f"Please provide a type in param `{dep.parent.param_name}`"
137137
+ f" of `{dep.parent.dependency}`",
138138
)
139-
# We zip together names of parameters and the subsctituted values
140-
# In parameters we would see TypeVars in args
141-
# we would find actual classes.
139+
# We zip together names of parameters and the substituted values
140+
# for generics.
142141
generics = zip(
143142
parent_cls_origin.__parameters__,
144143
parent_cls.__args__, # type: ignore
@@ -172,13 +171,14 @@ def _build_graph(self) -> None: # noqa: C901, WPS210
172171
# default vaule.
173172
for param_name, param in sign.parameters.items():
174173
default_value = param.default
174+
if hasattr(param.annotation, "__metadata__"): # noqa: WPS421
175+
for meta in param.annotation.__metadata__:
176+
if isinstance(meta, (Dependency, FastapiDepends)):
177+
default_value = meta
175178

176179
# This is for FastAPI integration. So you can
177180
# use Depends from taskiq mixed with fastapi's dependencies.
178-
if FastapiDepends is not None and isinstance( # noqa: WPS337
179-
default_value,
180-
FastapiDepends,
181-
):
181+
if isinstance(default_value, FastapiDepends):
182182
default_value = Dependency(
183183
dependency=default_value.dependency,
184184
use_cache=default_value.use_cache,

tests/test_annotated.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import sys
2+
3+
import pytest
4+
5+
if sys.version_info < (3, 10):
6+
pytest.skip("Annotated is available only for python 3.10+", allow_module_level=True)
7+
8+
from typing import Annotated, AsyncGenerator, Generic, TypeVar
9+
10+
from taskiq_dependencies import DependencyGraph, Depends
11+
12+
13+
def test_annotated_func() -> None:
14+
def get_int() -> int:
15+
return 1
16+
17+
def target_func(dep: Annotated[int, Depends(get_int)]) -> int:
18+
return dep
19+
20+
with DependencyGraph(target_func).sync_ctx() as ctx:
21+
res = target_func(**ctx.resolve_kwargs())
22+
assert res == 1
23+
24+
25+
def test_annotated_class() -> None:
26+
class TestClass:
27+
pass
28+
29+
def target_func(dep: Annotated[TestClass, Depends()]) -> TestClass:
30+
return dep
31+
32+
with DependencyGraph(target_func).sync_ctx() as ctx:
33+
res = target_func(**ctx.resolve_kwargs())
34+
assert isinstance(res, TestClass)
35+
36+
37+
def test_annotated_generic() -> None:
38+
_T = TypeVar("_T")
39+
40+
class MyClass:
41+
pass
42+
43+
class MainClass(Generic[_T]):
44+
def __init__(self, val: _T = Depends()) -> None:
45+
self.val = val
46+
47+
def test_func(a: Annotated[MainClass[MyClass], Depends()]) -> MyClass:
48+
return a.val
49+
50+
with DependencyGraph(target=test_func).sync_ctx(exception_propagation=False) as g:
51+
value = test_func(**(g.resolve_kwargs()))
52+
53+
assert isinstance(value, MyClass)
54+
55+
56+
@pytest.mark.anyio
57+
async def test_annotated_asyncgen() -> None:
58+
opened = False
59+
closed = False
60+
61+
async def my_gen() -> AsyncGenerator[int, None]:
62+
nonlocal opened, closed
63+
opened = True
64+
65+
yield 1
66+
67+
closed = True
68+
69+
def test_func(dep: Annotated[int, Depends(my_gen)]) -> int:
70+
return dep
71+
72+
async with DependencyGraph(target=test_func).async_ctx() as g:
73+
value = test_func(**(await g.resolve_kwargs()))
74+
assert value == 1
75+
76+
assert opened and closed

tests/test_fastapi.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import sys
12
from typing import Any
23
from unittest.mock import patch
34

5+
import pytest
6+
47
from taskiq_dependencies import DependencyGraph
58

69

@@ -29,3 +32,27 @@ def func_b(dep_a: int = MyFastapiDepends(func_a)) -> int: # type: ignore
2932
kwargs = ctx.resolve_kwargs()
3033

3134
assert kwargs == {"dep_a": 1}
35+
36+
37+
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Only for python 3.10+")
38+
def test_dependency_swap_annotated() -> None:
39+
"""
40+
Test that dependency classes are swapped.
41+
42+
This test checks that if function depends on FastAPI depends, it will
43+
be swapped and resolved.
44+
"""
45+
from typing import Annotated
46+
47+
with patch("taskiq_dependencies.graph.FastapiDepends", MyFastapiDepends):
48+
49+
def func_a() -> int:
50+
return 1
51+
52+
def func_b(dep_a: Annotated[int, MyFastapiDepends(func_a)]) -> int: # type: ignore
53+
return dep_a
54+
55+
with DependencyGraph(func_b).sync_ctx() as ctx:
56+
kwargs = ctx.resolve_kwargs()
57+
58+
assert kwargs == {"dep_a": 1}

0 commit comments

Comments
 (0)