Skip to content

Commit 2c0aede

Browse files
committed
Merge branch 'release/4.48.1'
2 parents 9d69943 + 84a14f2 commit 2c0aede

File tree

8 files changed

+87
-77
lines changed

8 files changed

+87
-77
lines changed

.github/workflows/publishing.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ jobs:
7070
steps:
7171
- uses: actions/checkout@v3
7272
- name: Build wheels
73-
uses: pypa/cibuildwheel@v2.23.3
73+
uses: pypa/cibuildwheel@v3.0.0
7474
- uses: actions/upload-artifact@v4
7575
with:
76-
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
76+
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
7777
path: ./wheelhouse/*.whl
7878

7979
test-publish:

docs/main/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
4.48.1
11+
------
12+
13+
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
14+
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
15+
* Produce warning on ``@inject``s without ``Provide[...]`` marks
16+
* Add support for `resource_type` in ``Lifespan``s
17+
1018
4.48.0
1119
------
1220

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ classifiers = [
5252
"Topic :: Software Development :: Libraries :: Python Modules",
5353
]
5454
dynamic = ["version"]
55+
dependencies = [
56+
# typing.Annotated since v3.9
57+
# typing.Self since v3.11
58+
"typing-extensions; python_version<'3.11'",
59+
]
5560

5661
[project.optional-dependencies]
5762
yaml = ["pyyaml"]
@@ -108,6 +113,7 @@ markers = [
108113
"pydantic: Tests with Pydantic as a dependency",
109114
]
110115
filterwarnings = [
116+
"ignore::dependency_injector.wiring.DIWiringWarning",
111117
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
112118
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
113119
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",

src/dependency_injector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Top-level package."""
22

3-
__version__ = "4.48.0"
3+
__version__ = "4.48.1"
44
"""Version number.
55
66
:type: str

src/dependency_injector/_cwiring.pyx

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,11 @@ from collections.abc import Awaitable
55
from inspect import CO_ITERABLE_COROUTINE
66
from types import CoroutineType, GeneratorType
77

8-
from .providers cimport Provider, Resource, NULL_AWAITABLE
8+
from .providers cimport Provider, Resource
99
from .wiring import _Marker
1010

11-
cimport cython
1211

13-
14-
@cython.internal
15-
@cython.no_gc
16-
cdef class KWPair:
17-
cdef str name
18-
cdef object value
19-
20-
def __cinit__(self, str name, object value, /):
21-
self.name = name
22-
self.value = value
23-
24-
25-
cdef inline bint _is_injectable(dict kwargs, str name):
12+
cdef inline bint _is_injectable(dict kwargs, object name):
2613
return name not in kwargs or isinstance(kwargs[name], _Marker)
2714

2815

@@ -38,11 +25,8 @@ cdef class DependencyResolver:
3825
self.injections = injections
3926
self.closings = closings
4027

41-
async def _await_injection(self, kw_pair: KWPair, /) -> None:
42-
self.to_inject[kw_pair.name] = await kw_pair.value
43-
44-
cdef object _await_injections(self, to_await: list):
45-
return gather(*map(self._await_injection, to_await))
28+
async def _await_injection(self, name: str, value: object, /) -> None:
29+
self.to_inject[name] = await value
4630

4731
cdef void _handle_injections_sync(self):
4832
cdef Provider provider
@@ -60,7 +44,7 @@ cdef class DependencyResolver:
6044
provide = provider()
6145

6246
if provider.is_async_mode_enabled() or _isawaitable(provide):
63-
to_await.append(KWPair(name, provide))
47+
to_await.append(self._await_injection(name, provide))
6448
else:
6549
self.to_inject[name] = provide
6650

@@ -93,13 +77,12 @@ cdef class DependencyResolver:
9377

9478
async def __aenter__(self):
9579
if to_await := self._handle_injections_async():
96-
await self._await_injections(to_await)
80+
await gather(*to_await)
9781
return self.to_inject
9882

99-
def __aexit__(self, *_):
83+
async def __aexit__(self, *_):
10084
if to_await := self._handle_closings_async():
101-
return gather(*to_await)
102-
return NULL_AWAITABLE
85+
await gather(*to_await)
10386

10487

10588
cdef bint _isawaitable(object instance):

src/dependency_injector/ext/starlette.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import sys
2-
from typing import Any
2+
from typing import Any, Type
33

44
if sys.version_info >= (3, 11): # pragma: no cover
55
from typing import Self
66
else: # pragma: no cover
77
from typing_extensions import Self
88

99
from dependency_injector.containers import Container
10+
from dependency_injector.providers import Resource
1011

1112

1213
class Lifespan:
@@ -29,24 +30,32 @@ class Container(DeclarativeContainer):
2930
app = Factory(Starlette, lifespan=lifespan)
3031
3132
:param container: container instance
33+
:param resource_type: A :py:class:`~dependency_injector.resources.Resource`
34+
subclass. Limits the resources to be initialized and shutdown.
3235
"""
3336

3437
container: Container
38+
resource_type: Type[Resource[Any]]
3539

36-
def __init__(self, container: Container) -> None:
40+
def __init__(
41+
self,
42+
container: Container,
43+
resource_type: Type[Resource[Any]] = Resource,
44+
) -> None:
3745
self.container = container
46+
self.resource_type = resource_type
3847

3948
def __call__(self, app: Any) -> Self:
4049
return self
4150

4251
async def __aenter__(self) -> None:
43-
result = self.container.init_resources()
52+
result = self.container.init_resources(self.resource_type)
4453

4554
if result is not None:
4655
await result
4756

4857
async def __aexit__(self, *exc_info: Any) -> None:
49-
result = self.container.shutdown_resources()
58+
result = self.container.shutdown_resources(self.resource_type)
5059

5160
if result is not None:
5261
await result

src/dependency_injector/wiring.py

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import inspect
77
import pkgutil
88
import sys
9+
from contextlib import suppress
10+
from inspect import isbuiltin, isclass
911
from types import ModuleType
1012
from typing import (
1113
TYPE_CHECKING,
@@ -15,6 +17,7 @@
1517
Dict,
1618
Iterable,
1719
Iterator,
20+
List,
1821
Optional,
1922
Protocol,
2023
Set,
@@ -24,6 +27,7 @@
2427
Union,
2528
cast,
2629
)
30+
from warnings import warn
2731

2832
try:
2933
from typing import Self
@@ -59,13 +63,11 @@ def get_origin(tp):
5963
return None
6064

6165

62-
MARKER_EXTRACTORS = []
66+
MARKER_EXTRACTORS: List[Callable[[Any], Any]] = []
67+
INSPECT_EXCLUSION_FILTERS: List[Callable[[Any], bool]] = [isbuiltin]
6368

64-
try:
69+
with suppress(ImportError):
6570
from fastapi.params import Depends as FastAPIDepends
66-
except ImportError:
67-
pass
68-
else:
6971

7072
def extract_marker_from_fastapi(param: Any) -> Any:
7173
if isinstance(param, FastAPIDepends):
@@ -74,11 +76,8 @@ def extract_marker_from_fastapi(param: Any) -> Any:
7476

7577
MARKER_EXTRACTORS.append(extract_marker_from_fastapi)
7678

77-
try:
79+
with suppress(ImportError):
7880
from fast_depends.dependencies import Depends as FastDepends
79-
except ImportError:
80-
pass
81-
else:
8281

8382
def extract_marker_from_fast_depends(param: Any) -> Any:
8483
if isinstance(param, FastDepends):
@@ -88,16 +87,22 @@ def extract_marker_from_fast_depends(param: Any) -> Any:
8887
MARKER_EXTRACTORS.append(extract_marker_from_fast_depends)
8988

9089

91-
try:
92-
import starlette.requests
93-
except ImportError:
94-
starlette = None
90+
with suppress(ImportError):
91+
from starlette.requests import Request as StarletteRequest
9592

93+
def is_starlette_request_cls(obj: Any) -> bool:
94+
return isclass(obj) and _safe_is_subclass(obj, StarletteRequest)
9695

97-
try:
98-
import werkzeug.local
99-
except ImportError:
100-
werkzeug = None
96+
INSPECT_EXCLUSION_FILTERS.append(is_starlette_request_cls)
97+
98+
99+
with suppress(ImportError):
100+
from werkzeug.local import LocalProxy as WerkzeugLocalProxy
101+
102+
def is_werkzeug_local_proxy(obj: Any) -> bool:
103+
return isinstance(obj, WerkzeugLocalProxy)
104+
105+
INSPECT_EXCLUSION_FILTERS.append(is_werkzeug_local_proxy)
101106

102107
from . import providers # noqa: E402
103108

@@ -130,6 +135,10 @@ def extract_marker_from_fast_depends(param: Any) -> Any:
130135
Container = Any
131136

132137

138+
class DIWiringWarning(RuntimeWarning):
139+
"""Base class for all warnings raised by the wiring module."""
140+
141+
133142
class PatchedRegistry:
134143

135144
def __init__(self) -> None:
@@ -411,30 +420,11 @@ def _create_providers_map(
411420
return providers_map
412421

413422

414-
class InspectFilter:
415-
416-
def is_excluded(self, instance: object) -> bool:
417-
if self._is_werkzeug_local_proxy(instance):
418-
return True
419-
elif self._is_starlette_request_cls(instance):
423+
def is_excluded_from_inspect(obj: Any) -> bool:
424+
for is_excluded in INSPECT_EXCLUSION_FILTERS:
425+
if is_excluded(obj):
420426
return True
421-
elif self._is_builtin(instance):
422-
return True
423-
else:
424-
return False
425-
426-
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
427-
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
428-
429-
def _is_starlette_request_cls(self, instance: object) -> bool:
430-
return (
431-
starlette
432-
and isinstance(instance, type)
433-
and _safe_is_subclass(instance, starlette.requests.Request)
434-
)
435-
436-
def _is_builtin(self, instance: object) -> bool:
437-
return inspect.isbuiltin(instance)
427+
return False
438428

439429

440430
def wire( # noqa: C901
@@ -455,7 +445,7 @@ def wire( # noqa: C901
455445

456446
for module in modules:
457447
for member_name, member in _get_members_and_annotated(module):
458-
if _inspect_filter.is_excluded(member):
448+
if is_excluded_from_inspect(member):
459449
continue
460450

461451
if _is_marker(member):
@@ -520,6 +510,11 @@ def unwire( # noqa: C901
520510
def inject(fn: F) -> F:
521511
"""Decorate callable with injecting decorator."""
522512
reference_injections, reference_closing = _fetch_reference_injections(fn)
513+
514+
if not reference_injections:
515+
warn("@inject is not required here", DIWiringWarning, stacklevel=2)
516+
return fn
517+
523518
patched = _get_patched(fn, reference_injections, reference_closing)
524519
return cast(F, patched)
525520

@@ -1054,7 +1049,6 @@ def is_loader_installed() -> bool:
10541049

10551050

10561051
_patched_registry = PatchedRegistry()
1057-
_inspect_filter = InspectFilter()
10581052
_loader = AutoLoader()
10591053

10601054
# Optimizations

tests/unit/ext/test_starlette.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import AsyncIterator, Iterator
1+
from typing import AsyncIterator, Iterator, TypeVar
22
from unittest.mock import ANY
33

44
from pytest import mark
@@ -7,6 +7,12 @@
77
from dependency_injector.ext.starlette import Lifespan
88
from dependency_injector.providers import Resource
99

10+
T = TypeVar("T")
11+
12+
13+
class XResource(Resource[T]):
14+
"""A test provider"""
15+
1016

1117
class TestLifespan:
1218
@mark.parametrize("sync", [False, True])
@@ -28,11 +34,15 @@ async def async_resource() -> AsyncIterator[None]:
2834
yield
2935
shutdown = True
3036

37+
def nope():
38+
assert False, "should not be called"
39+
3140
class Container(DeclarativeContainer):
32-
x = Resource(sync_resource if sync else async_resource)
41+
x = XResource(sync_resource if sync else async_resource)
42+
y = Resource(nope)
3343

3444
container = Container()
35-
lifespan = Lifespan(container)
45+
lifespan = Lifespan(container, resource_type=XResource)
3646

3747
async with lifespan(ANY) as scope:
3848
assert scope is None

0 commit comments

Comments
 (0)