Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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+
withsuppress(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+
withsuppress(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)

AltStyle によって変換されたページ (->オリジナル) /