Skip to content

Commit e050ad4

Browse files
committed
Merge branch 'develop' into fix/nested-resource-resolution
2 parents f8ab127 + 8b625d8 commit e050ad4

File tree

10 files changed

+113
-62
lines changed

10 files changed

+113
-62
lines changed

docs/wiring.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ FastAPI example:
6464
6565
@app.api_route("/")
6666
@inject
67-
async def index(service: Service = Depends(Provide[Container.service])):
67+
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
6868
value = await service.process()
6969
return {"result": value}
7070

examples/miniapps/fastapi-redis/fastapiredis/application.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
"""Application module."""
22

3-
from dependency_injector.wiring import inject, Provide
4-
from fastapi import FastAPI, Depends
3+
from typing import Annotated
4+
5+
from fastapi import Depends, FastAPI
6+
7+
from dependency_injector.wiring import Provide, inject
58

69
from .containers import Container
710
from .services import Service
811

9-
1012
app = FastAPI()
1113

1214

1315
@app.api_route("/")
1416
@inject
15-
async def index(service: Service = Depends(Provide[Container.service])):
17+
async def index(
18+
service: Annotated[Service, Depends(Provide[Container.service])]
19+
) -> dict[str, str]:
1620
value = await service.process()
1721
return {"result": value}
1822

examples/miniapps/fastapi-simple/fastapi_di_example.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from fastapi import FastAPI, Depends
1+
from typing import Annotated
2+
3+
from fastapi import Depends, FastAPI
4+
25
from dependency_injector import containers, providers
36
from dependency_injector.wiring import Provide, inject
47

@@ -18,7 +21,9 @@ class Container(containers.DeclarativeContainer):
1821

1922
@app.api_route("/")
2023
@inject
21-
async def index(service: Service = Depends(Provide[Container.service])):
24+
async def index(
25+
service: Annotated[Service, Depends(Provide[Container.service])]
26+
) -> dict[str, str]:
2227
result = await service.process()
2328
return {"result": result}
2429

examples/miniapps/fastapi-sqlalchemy/webapp/endpoints.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
"""Endpoints module."""
22

3+
from typing import Annotated
4+
35
from fastapi import APIRouter, Depends, Response, status
4-
from dependency_injector.wiring import inject, Provide
6+
7+
from dependency_injector.wiring import Provide, inject
58

69
from .containers import Container
7-
from .services import UserService
810
from .repositories import NotFoundError
11+
from .services import UserService
912

1013
router = APIRouter()
1114

1215

1316
@router.get("/users")
1417
@inject
1518
def get_list(
16-
user_service: UserService = Depends(Provide[Container.user_service]),
19+
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
1720
):
1821
return user_service.get_users()
1922

2023

2124
@router.get("/users/{user_id}")
2225
@inject
2326
def get_by_id(
24-
user_id: int,
25-
user_service: UserService = Depends(Provide[Container.user_service]),
27+
user_id: int,
28+
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
2629
):
2730
try:
2831
return user_service.get_user_by_id(user_id)
@@ -33,17 +36,17 @@ def get_by_id(
3336
@router.post("/users", status_code=status.HTTP_201_CREATED)
3437
@inject
3538
def add(
36-
user_service: UserService = Depends(Provide[Container.user_service]),
39+
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
3740
):
3841
return user_service.create_user()
3942

4043

4144
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
4245
@inject
4346
def remove(
44-
user_id: int,
45-
user_service: UserService = Depends(Provide[Container.user_service]),
46-
):
47+
user_id: int,
48+
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
49+
) -> Response:
4750
try:
4851
user_service.delete_user_by_id(user_id)
4952
except NotFoundError:

examples/miniapps/fastapi/giphynavigator/endpoints.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""Endpoints module."""
22

3-
from typing import Optional, List
3+
from typing import Annotated, List
44

55
from fastapi import APIRouter, Depends
66
from pydantic import BaseModel
7-
from dependency_injector.wiring import inject, Provide
87

9-
from .services import SearchService
8+
from dependency_injector.wiring import Provide, inject
9+
1010
from .containers import Container
11+
from .services import SearchService
1112

1213

1314
class Gif(BaseModel):
@@ -26,11 +27,15 @@ class Response(BaseModel):
2627
@router.get("/", response_model=Response)
2728
@inject
2829
async def index(
29-
query: Optional[str] = None,
30-
limit: Optional[str] = None,
31-
default_query: str = Depends(Provide[Container.config.default.query]),
32-
default_limit: int = Depends(Provide[Container.config.default.limit.as_int()]),
33-
search_service: SearchService = Depends(Provide[Container.search_service]),
30+
default_query: Annotated[str, Depends(Provide[Container.config.default.query])],
31+
default_limit: Annotated[
32+
int, Depends(Provide[Container.config.default.limit.as_int()])
33+
],
34+
search_service: Annotated[
35+
SearchService, Depends(Provide[Container.search_service])
36+
],
37+
query: str | None = None,
38+
limit: int | None = None,
3439
):
3540
query = query or default_query
3641
limit = limit or default_limit

src/dependency_injector/_cwiring.pyx

+22-27
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,39 @@
22

33
import asyncio
44
import collections.abc
5-
import functools
65
import inspect
76
import types
87

9-
from . import providers
10-
from .wiring import _Marker, PatchedCallable
8+
from .wiring import _Marker
119

12-
from .providers cimport Provider
10+
from .providers cimport Provider, Resource
1311

1412

15-
def _get_sync_patched(fn, patched: PatchedCallable):
16-
@functools.wraps(fn)
17-
def _patched(*args, **kwargs):
18-
cdef object result
19-
cdef dict to_inject
20-
cdef object arg_key
21-
cdef Provider provider
13+
def _sync_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /):
14+
cdef object result
15+
cdef dict to_inject
16+
cdef object arg_key
17+
cdef Provider provider
2218

23-
to_inject = kwargs.copy()
24-
for arg_key, provider in patched.injections.items():
25-
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
26-
to_inject[arg_key] = provider()
19+
to_inject = kwargs.copy()
20+
for arg_key, provider in injections.items():
21+
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
22+
to_inject[arg_key] = provider()
2723

28-
result = fn(*args, **to_inject)
24+
result = fn(*args, **to_inject)
2925

30-
if patched.closing:
31-
for arg_key, provider in patched.closing.items():
32-
if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker):
33-
continue
34-
if not isinstance(provider, providers.Resource):
35-
continue
36-
provider.shutdown()
26+
if closings:
27+
for arg_key, provider in closings.items():
28+
if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker):
29+
continue
30+
if not isinstance(provider, Resource):
31+
continue
32+
provider.shutdown()
3733

38-
return result
39-
return _patched
34+
return result
4035

4136

42-
async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings):
37+
async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /):
4338
cdef object result
4439
cdef dict to_inject
4540
cdef list to_inject_await = []
@@ -69,7 +64,7 @@ async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dic
6964
for arg_key, provider in closings.items():
7065
if arg_key in kwargs and isinstance(kwargs[arg_key], _Marker):
7166
continue
72-
if not isinstance(provider, providers.Resource):
67+
if not isinstance(provider, Resource):
7368
continue
7469
shutdown = provider.shutdown()
7570
if _isawaitable(shutdown):

src/dependency_injector/providers.pyx

+13-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import absolute_import
44

5+
import asyncio
56
import copy
67
import errno
78
import functools
@@ -27,17 +28,19 @@ except ImportError:
2728
import __builtin__ as builtins
2829

2930
try:
30-
import asyncio
31+
from inspect import _is_coroutine_mark as _is_coroutine_marker
3132
except ImportError:
32-
asyncio = None
33-
_is_coroutine_marker = None
34-
else:
35-
if sys.version_info >= (3, 5, 3):
36-
import asyncio.coroutines
37-
_is_coroutine_marker = asyncio.coroutines._is_coroutine
38-
else:
33+
try:
34+
# Python >=3.12.0,<3.12.5
35+
from inspect import _is_coroutine_marker
36+
except ImportError:
3937
_is_coroutine_marker = True
4038

39+
try:
40+
from asyncio.coroutines import _is_coroutine
41+
except ImportError:
42+
_is_coroutine = True
43+
4144
try:
4245
import ConfigParser as iniconfigparser
4346
except ImportError:
@@ -1475,7 +1478,8 @@ cdef class Coroutine(Callable):
14751478
some_coroutine.add_kwargs(keyword_argument1=3, keyword_argument=4)
14761479
"""
14771480

1478-
_is_coroutine = _is_coroutine_marker
1481+
_is_coroutine_marker = _is_coroutine_marker # Python >=3.12
1482+
_is_coroutine = _is_coroutine # Python <3.16
14791483

14801484
def set_provides(self, provides):
14811485
"""Set provider provides."""

src/dependency_injector/wiring.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,8 @@ def is_loader_installed() -> bool:
10281028
_loader = AutoLoader()
10291029

10301030
# Optimizations
1031+
from ._cwiring import _sync_inject # noqa
10311032
from ._cwiring import _async_inject # noqa
1032-
from ._cwiring import _get_sync_patched # noqa
10331033

10341034

10351035
# Wiring uses the following Python wrapper because there is
@@ -1045,4 +1045,17 @@ async def _patched(*args, **kwargs):
10451045
patched.closing,
10461046
)
10471047

1048-
return _patched
1048+
return cast(F, _patched)
1049+
1050+
1051+
def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
1052+
@functools.wraps(fn)
1053+
def _patched(*args, **kwargs):
1054+
return _sync_inject(
1055+
fn,
1056+
args,
1057+
kwargs,
1058+
patched.injections,
1059+
patched.closing,
1060+
)
1061+
return cast(F, _patched)

tests/unit/providers/coroutines/test_coroutine_py35.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Coroutine provider tests."""
2+
import sys
23

34
from dependency_injector import providers, errors
45
from pytest import mark, raises
@@ -208,3 +209,17 @@ def test_repr():
208209
"<dependency_injector.providers."
209210
"Coroutine({0}) at {1}>".format(repr(example), hex(id(provider)))
210211
)
212+
213+
214+
@mark.skipif(sys.version_info > (3, 15), reason="requires Python<3.16")
215+
def test_asyncio_iscoroutinefunction() -> None:
216+
from asyncio.coroutines import iscoroutinefunction
217+
218+
assert iscoroutinefunction(providers.Coroutine(example))
219+
220+
221+
@mark.skipif(sys.version_info < (3, 12), reason="requires Python>=3.12")
222+
def test_inspect_iscoroutinefunction() -> None:
223+
from inspect import iscoroutinefunction
224+
225+
assert iscoroutinefunction(providers.Coroutine(example))

tests/unit/wiring/test_introspection_py36.py

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
from dependency_injector.wiring import inject
77

88

9+
def test_isfunction():
10+
@inject
11+
def foo(): ...
12+
13+
assert inspect.isfunction(foo)
14+
15+
916
def test_asyncio_iscoroutinefunction():
1017
@inject
1118
async def foo():

0 commit comments

Comments
 (0)