anydi 0.25.0a1__tar.gz → 0.25.0a2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anydi-0.25.0a1 → anydi-0.25.0a2}/PKG-INFO +57 -1
- {anydi-0.25.0a1 → anydi-0.25.0a2}/README.md +56 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_container.py +37 -50
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_context.py +8 -8
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_module.py +4 -4
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_scanner.py +19 -27
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_types.py +6 -4
- anydi-0.25.0a2/anydi/_utils.py +111 -0
- anydi-0.25.0a2/anydi/ext/django/__init__.py +9 -0
- anydi-0.25.0a2/anydi/ext/django/_settings.py +39 -0
- anydi-0.25.0a2/anydi/ext/django/_utils.py +111 -0
- anydi-0.25.0a2/anydi/ext/django/apps.py +82 -0
- anydi-0.25.0a2/anydi/ext/django/middleware.py +26 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/django/ninja/__init__.py +1 -1
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/django/ninja/_operation.py +2 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/django/ninja/_signature.py +2 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/fastapi.py +2 -2
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/pytest_plugin.py +12 -10
- {anydi-0.25.0a1 → anydi-0.25.0a2}/pyproject.toml +2 -2
- anydi-0.25.0a1/anydi/_utils.py +0 -111
- anydi-0.25.0a1/anydi/ext/django/__init__.py +0 -3
- anydi-0.25.0a1/anydi/ext/django/_utils.py +0 -14
- anydi-0.25.0a1/anydi/ext/django/apps.py +0 -177
- {anydi-0.25.0a1 → anydi-0.25.0a2}/LICENSE +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/__init__.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/_logger.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/__init__.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/django/_container.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.25.0a1 → anydi-0.25.0a2}/anydi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anydi
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.0a2
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Home-page: https://github.com/antonrh/anydi
|
|
6
6
|
License: MIT
|
|
@@ -139,3 +139,59 @@ def say_hello(message: str = Inject()) -> dict[str, str]:
|
|
|
139
139
|
anydi.ext.fastapi.install(app, container)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## Django Ninja Example
|
|
145
|
+
|
|
146
|
+
*container.py*
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from anydi import Container
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_container() -> Container:
|
|
153
|
+
container = Container()
|
|
154
|
+
|
|
155
|
+
@container.provider(scope="singleton")
|
|
156
|
+
def message() -> str:
|
|
157
|
+
return "Hello, World!"
|
|
158
|
+
|
|
159
|
+
return container
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
*settings.py*
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
INSTALLED_APPS = [
|
|
166
|
+
...
|
|
167
|
+
"anydi.ext.django",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
ANYDI = {
|
|
171
|
+
"CONTAINER_FACTORY": "myapp.container.get_container",
|
|
172
|
+
"PATCH_NINJA": True,
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
*urls.py*
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from django.http import HttpRequest
|
|
180
|
+
from django.urls import path
|
|
181
|
+
from ninja import NinjaAPI
|
|
182
|
+
|
|
183
|
+
from anydi import auto
|
|
184
|
+
|
|
185
|
+
api = NinjaAPI()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@api.get("/hello")
|
|
189
|
+
def say_hello(request: HttpRequest, message: str = auto) -> dict[str, str]:
|
|
190
|
+
return {"message": message}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
urlpatterns = [
|
|
194
|
+
path("api/", api.urls),
|
|
195
|
+
]
|
|
196
|
+
```
|
|
197
|
+
|
|
@@ -98,3 +98,59 @@ def say_hello(message: str = Inject()) -> dict[str, str]:
|
|
|
98
98
|
# Install the container into the FastAPI app
|
|
99
99
|
anydi.ext.fastapi.install(app, container)
|
|
100
100
|
```
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Django Ninja Example
|
|
105
|
+
|
|
106
|
+
*container.py*
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from anydi import Container
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_container() -> Container:
|
|
113
|
+
container = Container()
|
|
114
|
+
|
|
115
|
+
@container.provider(scope="singleton")
|
|
116
|
+
def message() -> str:
|
|
117
|
+
return "Hello, World!"
|
|
118
|
+
|
|
119
|
+
return container
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
*settings.py*
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
INSTALLED_APPS = [
|
|
126
|
+
...
|
|
127
|
+
"anydi.ext.django",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
ANYDI = {
|
|
131
|
+
"CONTAINER_FACTORY": "myapp.container.get_container",
|
|
132
|
+
"PATCH_NINJA": True,
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
*urls.py*
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from django.http import HttpRequest
|
|
140
|
+
from django.urls import path
|
|
141
|
+
from ninja import NinjaAPI
|
|
142
|
+
|
|
143
|
+
from anydi import auto
|
|
144
|
+
|
|
145
|
+
api = NinjaAPI()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@api.get("/hello")
|
|
149
|
+
def say_hello(request: HttpRequest, message: str = auto) -> dict[str, str]:
|
|
150
|
+
return {"message": message}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
urlpatterns = [
|
|
154
|
+
path("api/", api.urls),
|
|
155
|
+
]
|
|
156
|
+
```
|
|
@@ -15,21 +15,16 @@ from typing import (
|
|
|
15
15
|
Awaitable,
|
|
16
16
|
Callable,
|
|
17
17
|
ContextManager,
|
|
18
|
-
Dict,
|
|
19
18
|
Iterable,
|
|
20
19
|
Iterator,
|
|
21
|
-
List,
|
|
22
20
|
Mapping,
|
|
23
|
-
Optional,
|
|
24
21
|
Sequence,
|
|
25
|
-
Type,
|
|
26
22
|
TypeVar,
|
|
27
|
-
Union,
|
|
28
23
|
cast,
|
|
29
24
|
overload,
|
|
30
25
|
)
|
|
31
26
|
|
|
32
|
-
from typing_extensions import
|
|
27
|
+
from typing_extensions import ParamSpec, final, get_args, get_origin
|
|
33
28
|
|
|
34
29
|
try:
|
|
35
30
|
from types import NoneType
|
|
@@ -48,12 +43,17 @@ from ._logger import logger
|
|
|
48
43
|
from ._module import Module, ModuleRegistry
|
|
49
44
|
from ._scanner import Scanner
|
|
50
45
|
from ._types import AnyInterface, Interface, Provider, Scope, is_marker
|
|
51
|
-
from ._utils import
|
|
46
|
+
from ._utils import (
|
|
47
|
+
get_full_qualname,
|
|
48
|
+
get_typed_parameters,
|
|
49
|
+
get_typed_return_annotation,
|
|
50
|
+
is_builtin_type,
|
|
51
|
+
)
|
|
52
52
|
|
|
53
53
|
T = TypeVar("T", bound=Any)
|
|
54
54
|
P = ParamSpec("P")
|
|
55
55
|
|
|
56
|
-
ALLOWED_SCOPES:
|
|
56
|
+
ALLOWED_SCOPES: dict[Scope, list[Scope]] = {
|
|
57
57
|
"singleton": ["singleton"],
|
|
58
58
|
"request": ["request", "singleton"],
|
|
59
59
|
"transient": ["transient", "singleton", "request"],
|
|
@@ -71,10 +71,9 @@ class Container:
|
|
|
71
71
|
def __init__(
|
|
72
72
|
self,
|
|
73
73
|
*,
|
|
74
|
-
providers:
|
|
75
|
-
modules:
|
|
76
|
-
|
|
77
|
-
] = None,
|
|
74
|
+
providers: Mapping[type[Any], Provider] | None = None,
|
|
75
|
+
modules: Sequence[Module | type[Module] | Callable[[Container], None]]
|
|
76
|
+
| None = None,
|
|
78
77
|
strict: bool = False,
|
|
79
78
|
) -> None:
|
|
80
79
|
"""Initialize the AnyDI instance.
|
|
@@ -84,13 +83,13 @@ class Container:
|
|
|
84
83
|
modules: Optional sequence of modules to register during initialization.
|
|
85
84
|
strict: Whether to enable strict mode. Defaults to False.
|
|
86
85
|
"""
|
|
87
|
-
self._providers:
|
|
86
|
+
self._providers: dict[type[Any], Provider] = {}
|
|
88
87
|
self._singleton_context = SingletonContext(self)
|
|
89
88
|
self._transient_context = TransientContext(self)
|
|
90
|
-
self._request_context_var: ContextVar[
|
|
89
|
+
self._request_context_var: ContextVar[RequestContext | None] = ContextVar(
|
|
91
90
|
"request_context", default=None
|
|
92
91
|
)
|
|
93
|
-
self._override_instances:
|
|
92
|
+
self._override_instances: dict[type[Any], Any] = {}
|
|
94
93
|
self._strict = strict
|
|
95
94
|
|
|
96
95
|
# Components
|
|
@@ -117,7 +116,7 @@ class Container:
|
|
|
117
116
|
return self._strict
|
|
118
117
|
|
|
119
118
|
@property
|
|
120
|
-
def providers(self) ->
|
|
119
|
+
def providers(self) -> dict[type[Any], Provider]:
|
|
121
120
|
"""Get the registered providers.
|
|
122
121
|
|
|
123
122
|
Returns:
|
|
@@ -333,7 +332,7 @@ class Container:
|
|
|
333
332
|
"""
|
|
334
333
|
related_providers = []
|
|
335
334
|
|
|
336
|
-
for parameter in provider.parameters
|
|
335
|
+
for parameter in provider.parameters:
|
|
337
336
|
if parameter.annotation is inspect._empty: # noqa
|
|
338
337
|
raise TypeError(
|
|
339
338
|
f"Missing provider `{provider}` "
|
|
@@ -363,7 +362,7 @@ class Container:
|
|
|
363
362
|
"registered with matching scopes."
|
|
364
363
|
)
|
|
365
364
|
|
|
366
|
-
def _detect_scope(self, obj: Callable[..., Any]) ->
|
|
365
|
+
def _detect_scope(self, obj: Callable[..., Any]) -> Scope | None:
|
|
367
366
|
"""Detect the scope for a provider.
|
|
368
367
|
|
|
369
368
|
Args:
|
|
@@ -372,7 +371,7 @@ class Container:
|
|
|
372
371
|
The auto scope, or None if the auto scope cannot be detected.
|
|
373
372
|
"""
|
|
374
373
|
has_transient, has_request, has_singleton = False, False, False
|
|
375
|
-
for parameter in
|
|
374
|
+
for parameter in get_typed_parameters(obj):
|
|
376
375
|
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
377
376
|
if not has_transient and sub_provider.scope == "transient":
|
|
378
377
|
has_transient = True
|
|
@@ -389,7 +388,7 @@ class Container:
|
|
|
389
388
|
return None
|
|
390
389
|
|
|
391
390
|
def register_module(
|
|
392
|
-
self, module:
|
|
391
|
+
self, module: Module | type[Module] | Callable[[Container], None]
|
|
393
392
|
) -> None:
|
|
394
393
|
"""Register a module as a callable, module type, or module instance.
|
|
395
394
|
|
|
@@ -633,14 +632,11 @@ class Container:
|
|
|
633
632
|
def inject(self) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
|
634
633
|
|
|
635
634
|
def inject(
|
|
636
|
-
self, obj:
|
|
637
|
-
) ->
|
|
638
|
-
Callable[
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
],
|
|
642
|
-
Callable[P, Union[T, Awaitable[T]]],
|
|
643
|
-
]:
|
|
635
|
+
self, obj: Callable[P, T | Awaitable[T]] | None = None
|
|
636
|
+
) -> (
|
|
637
|
+
Callable[[Callable[P, T | Awaitable[T]]], Callable[P, T | Awaitable[T]]]
|
|
638
|
+
| Callable[P, T | Awaitable[T]]
|
|
639
|
+
):
|
|
644
640
|
"""Decorator to inject dependencies into a callable.
|
|
645
641
|
|
|
646
642
|
Args:
|
|
@@ -652,8 +648,8 @@ class Container:
|
|
|
652
648
|
"""
|
|
653
649
|
|
|
654
650
|
def decorator(
|
|
655
|
-
obj: Callable[P,
|
|
656
|
-
) -> Callable[P,
|
|
651
|
+
obj: Callable[P, T | Awaitable[T]],
|
|
652
|
+
) -> Callable[P, T | Awaitable[T]]:
|
|
657
653
|
injected_params = self._get_injected_params(obj)
|
|
658
654
|
|
|
659
655
|
if inspect.iscoroutinefunction(obj):
|
|
@@ -694,12 +690,9 @@ class Container:
|
|
|
694
690
|
def scan(
|
|
695
691
|
self,
|
|
696
692
|
/,
|
|
697
|
-
packages:
|
|
698
|
-
Union[types.ModuleType, str],
|
|
699
|
-
Iterable[Union[types.ModuleType, str]],
|
|
700
|
-
],
|
|
693
|
+
packages: types.ModuleType | str | Iterable[types.ModuleType | str],
|
|
701
694
|
*,
|
|
702
|
-
tags:
|
|
695
|
+
tags: Iterable[str] | None = None,
|
|
703
696
|
) -> None:
|
|
704
697
|
"""Scan packages or modules for decorated members and inject dependencies.
|
|
705
698
|
|
|
@@ -723,32 +716,26 @@ class Container:
|
|
|
723
716
|
Raises:
|
|
724
717
|
TypeError: If the provider return annotation is missing or invalid.
|
|
725
718
|
"""
|
|
726
|
-
annotation =
|
|
719
|
+
annotation = get_typed_return_annotation(obj)
|
|
727
720
|
|
|
728
|
-
if annotation is
|
|
721
|
+
if annotation is None:
|
|
729
722
|
raise TypeError(
|
|
730
723
|
f"Missing `{get_full_qualname(obj)}` provider return annotation."
|
|
731
724
|
)
|
|
732
725
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
# Supported generic types
|
|
737
|
-
if origin in (list, dict, tuple, Annotated):
|
|
726
|
+
if get_origin(annotation) in (get_origin(Iterator), get_origin(AsyncIterator)):
|
|
727
|
+
args = get_args(annotation)
|
|
738
728
|
if args:
|
|
739
|
-
return
|
|
729
|
+
return args[0]
|
|
740
730
|
else:
|
|
741
731
|
raise TypeError(
|
|
742
|
-
f"Cannot use `{get_full_qualname(obj)}`
|
|
732
|
+
f"Cannot use `{get_full_qualname(obj)}` resource type annotation "
|
|
743
733
|
"without actual type."
|
|
744
734
|
)
|
|
745
735
|
|
|
746
|
-
|
|
747
|
-
return args[0]
|
|
748
|
-
except IndexError:
|
|
749
|
-
return annotation
|
|
736
|
+
return annotation
|
|
750
737
|
|
|
751
|
-
def _get_injected_params(self, obj: Callable[..., Any]) ->
|
|
738
|
+
def _get_injected_params(self, obj: Callable[..., Any]) -> dict[str, Any]:
|
|
752
739
|
"""Get the injected parameters of a callable object.
|
|
753
740
|
|
|
754
741
|
Args:
|
|
@@ -759,7 +746,7 @@ class Container:
|
|
|
759
746
|
of the injected parameters.
|
|
760
747
|
"""
|
|
761
748
|
injected_params = {}
|
|
762
|
-
for parameter in
|
|
749
|
+
for parameter in get_typed_parameters(obj):
|
|
763
750
|
if not is_marker(parameter.default):
|
|
764
751
|
continue
|
|
765
752
|
try:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import abc
|
|
4
4
|
import contextlib
|
|
5
5
|
from types import TracebackType
|
|
6
|
-
from typing import TYPE_CHECKING, Any,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
7
7
|
|
|
8
8
|
from typing_extensions import Self, final
|
|
9
9
|
|
|
@@ -87,7 +87,7 @@ class ScopedContext(abc.ABC):
|
|
|
87
87
|
|
|
88
88
|
def _get_provider_arguments(
|
|
89
89
|
self, provider: Provider
|
|
90
|
-
) ->
|
|
90
|
+
) -> tuple[list[Any], dict[str, Any]]:
|
|
91
91
|
"""Retrieve the arguments for a provider.
|
|
92
92
|
|
|
93
93
|
Args:
|
|
@@ -97,7 +97,7 @@ class ScopedContext(abc.ABC):
|
|
|
97
97
|
The arguments for the provider.
|
|
98
98
|
"""
|
|
99
99
|
args, kwargs = [], {}
|
|
100
|
-
for parameter in provider.parameters
|
|
100
|
+
for parameter in provider.parameters:
|
|
101
101
|
instance = self.container.resolve(parameter.annotation)
|
|
102
102
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
103
103
|
args.append(instance)
|
|
@@ -107,7 +107,7 @@ class ScopedContext(abc.ABC):
|
|
|
107
107
|
|
|
108
108
|
async def _aget_provider_arguments(
|
|
109
109
|
self, provider: Provider
|
|
110
|
-
) ->
|
|
110
|
+
) -> tuple[list[Any], dict[str, Any]]:
|
|
111
111
|
"""Asynchronously retrieve the arguments for a provider.
|
|
112
112
|
|
|
113
113
|
Args:
|
|
@@ -117,7 +117,7 @@ class ScopedContext(abc.ABC):
|
|
|
117
117
|
The arguments for the provider.
|
|
118
118
|
"""
|
|
119
119
|
args, kwargs = [], {}
|
|
120
|
-
for parameter in provider.parameters
|
|
120
|
+
for parameter in provider.parameters:
|
|
121
121
|
instance = await self.container.aresolve(parameter.annotation)
|
|
122
122
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
123
123
|
args.append(instance)
|
|
@@ -132,7 +132,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
132
132
|
def __init__(self, container: Container) -> None:
|
|
133
133
|
"""Initialize the ScopedContext."""
|
|
134
134
|
super().__init__(container)
|
|
135
|
-
self._instances:
|
|
135
|
+
self._instances: dict[type[Any], Any] = {}
|
|
136
136
|
self._stack = contextlib.ExitStack()
|
|
137
137
|
self._async_stack = contextlib.AsyncExitStack()
|
|
138
138
|
|
|
@@ -237,7 +237,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
237
237
|
|
|
238
238
|
def __exit__(
|
|
239
239
|
self,
|
|
240
|
-
exc_type:
|
|
240
|
+
exc_type: type[BaseException] | None,
|
|
241
241
|
exc_val: BaseException | None,
|
|
242
242
|
exc_tb: TracebackType | None,
|
|
243
243
|
) -> None:
|
|
@@ -265,7 +265,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
265
265
|
|
|
266
266
|
async def __aexit__(
|
|
267
267
|
self,
|
|
268
|
-
exc_type:
|
|
268
|
+
exc_type: type[BaseException] | None,
|
|
269
269
|
exc_val: BaseException | None,
|
|
270
270
|
exc_tb: TracebackType | None,
|
|
271
271
|
) -> None:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
|
7
7
|
|
|
8
8
|
from typing_extensions import Concatenate, NamedTuple, ParamSpec
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ class ModuleMeta(type):
|
|
|
24
24
|
and stores it in the `providers` attribute.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
def __new__(cls, name: str, bases:
|
|
27
|
+
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> Any:
|
|
28
28
|
"""Create a new instance of the ModuleMeta class.
|
|
29
29
|
|
|
30
30
|
This method extracts provider information from the class attributes and
|
|
@@ -49,7 +49,7 @@ class ModuleMeta(type):
|
|
|
49
49
|
class Module(metaclass=ModuleMeta):
|
|
50
50
|
"""A base class for defining AnyDI modules."""
|
|
51
51
|
|
|
52
|
-
providers:
|
|
52
|
+
providers: list[tuple[str, ProviderDecoratorArgs]]
|
|
53
53
|
|
|
54
54
|
def configure(self, container: Container) -> None:
|
|
55
55
|
"""Configure the AnyDI container with providers and their dependencies.
|
|
@@ -67,7 +67,7 @@ class ModuleRegistry:
|
|
|
67
67
|
self.container = container
|
|
68
68
|
|
|
69
69
|
def register(
|
|
70
|
-
self, module:
|
|
70
|
+
self, module: Module | type[Module] | Callable[[Container], None]
|
|
71
71
|
) -> None:
|
|
72
72
|
"""Register a module as a callable, module type, or module instance.
|
|
73
73
|
|
|
@@ -10,8 +10,6 @@ from typing import (
|
|
|
10
10
|
Any,
|
|
11
11
|
Callable,
|
|
12
12
|
Iterable,
|
|
13
|
-
List,
|
|
14
|
-
Optional,
|
|
15
13
|
TypeVar,
|
|
16
14
|
Union,
|
|
17
15
|
cast,
|
|
@@ -22,7 +20,7 @@ from typing import (
|
|
|
22
20
|
from typing_extensions import NamedTuple, ParamSpec
|
|
23
21
|
|
|
24
22
|
from ._types import is_marker
|
|
25
|
-
from ._utils import
|
|
23
|
+
from ._utils import get_typed_parameters
|
|
26
24
|
|
|
27
25
|
if TYPE_CHECKING:
|
|
28
26
|
from ._container import Container
|
|
@@ -56,9 +54,9 @@ class Scanner:
|
|
|
56
54
|
def scan(
|
|
57
55
|
self,
|
|
58
56
|
/,
|
|
59
|
-
packages:
|
|
57
|
+
packages: ModuleType | str | Iterable[ModuleType | str],
|
|
60
58
|
*,
|
|
61
|
-
tags:
|
|
59
|
+
tags: Iterable[str] | None = None,
|
|
62
60
|
) -> None:
|
|
63
61
|
"""Scan packages or modules for decorated members and inject dependencies.
|
|
64
62
|
|
|
@@ -68,10 +66,10 @@ class Scanner:
|
|
|
68
66
|
tags: Optional list of tags to filter the scanned members. Only members
|
|
69
67
|
with at least one matching tag will be scanned. Defaults to None.
|
|
70
68
|
"""
|
|
71
|
-
dependencies:
|
|
69
|
+
dependencies: list[Dependency] = []
|
|
72
70
|
|
|
73
71
|
if isinstance(packages, Iterable) and not isinstance(packages, str):
|
|
74
|
-
scan_packages: Iterable[
|
|
72
|
+
scan_packages: Iterable[ModuleType | str] = packages
|
|
75
73
|
else:
|
|
76
74
|
scan_packages = cast(Iterable[Union[ModuleType, str]], [packages])
|
|
77
75
|
|
|
@@ -84,10 +82,10 @@ class Scanner:
|
|
|
84
82
|
|
|
85
83
|
def _scan_package(
|
|
86
84
|
self,
|
|
87
|
-
package:
|
|
85
|
+
package: ModuleType | str,
|
|
88
86
|
*,
|
|
89
|
-
tags:
|
|
90
|
-
) ->
|
|
87
|
+
tags: Iterable[str] | None = None,
|
|
88
|
+
) -> list[Dependency]:
|
|
91
89
|
"""Scan a package or module for decorated members.
|
|
92
90
|
|
|
93
91
|
Args:
|
|
@@ -107,7 +105,7 @@ class Scanner:
|
|
|
107
105
|
if not package_path:
|
|
108
106
|
return self._scan_module(package, tags=tags)
|
|
109
107
|
|
|
110
|
-
dependencies:
|
|
108
|
+
dependencies: list[Dependency] = []
|
|
111
109
|
|
|
112
110
|
for module_info in pkgutil.walk_packages(
|
|
113
111
|
path=package_path, prefix=package.__name__ + "."
|
|
@@ -119,7 +117,7 @@ class Scanner:
|
|
|
119
117
|
|
|
120
118
|
def _scan_module(
|
|
121
119
|
self, module: ModuleType, *, tags: Iterable[str]
|
|
122
|
-
) ->
|
|
120
|
+
) -> list[Dependency]:
|
|
123
121
|
"""Scan a module for decorated members.
|
|
124
122
|
|
|
125
123
|
Args:
|
|
@@ -130,7 +128,7 @@ class Scanner:
|
|
|
130
128
|
Returns:
|
|
131
129
|
A list of scanned dependencies.
|
|
132
130
|
"""
|
|
133
|
-
dependencies:
|
|
131
|
+
dependencies: list[Dependency] = []
|
|
134
132
|
|
|
135
133
|
for _, member in inspect.getmembers(module):
|
|
136
134
|
if getattr(member, "__module__", None) != module.__name__ or not callable(
|
|
@@ -159,10 +157,10 @@ class Scanner:
|
|
|
159
157
|
|
|
160
158
|
# Get by Marker
|
|
161
159
|
if inspect.isclass(member):
|
|
162
|
-
|
|
160
|
+
parameters = get_typed_parameters(member.__init__)
|
|
163
161
|
else:
|
|
164
|
-
|
|
165
|
-
for parameter in
|
|
162
|
+
parameters = get_typed_parameters(member)
|
|
163
|
+
for parameter in parameters:
|
|
166
164
|
if is_marker(parameter.default):
|
|
167
165
|
dependencies.append(
|
|
168
166
|
self._create_dependency(member=member, module=module)
|
|
@@ -188,7 +186,7 @@ class Scanner:
|
|
|
188
186
|
|
|
189
187
|
class InjectDecoratorArgs(NamedTuple):
|
|
190
188
|
wrapped: bool
|
|
191
|
-
tags:
|
|
189
|
+
tags: Iterable[str] | None
|
|
192
190
|
|
|
193
191
|
|
|
194
192
|
@overload
|
|
@@ -197,20 +195,14 @@ def injectable(obj: Callable[P, T]) -> Callable[P, T]: ...
|
|
|
197
195
|
|
|
198
196
|
@overload
|
|
199
197
|
def injectable(
|
|
200
|
-
*, tags:
|
|
198
|
+
*, tags: Iterable[str] | None = None
|
|
201
199
|
) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
|
202
200
|
|
|
203
201
|
|
|
204
202
|
def injectable(
|
|
205
|
-
obj:
|
|
206
|
-
tags:
|
|
207
|
-
) ->
|
|
208
|
-
Callable[
|
|
209
|
-
[Callable[P, T]],
|
|
210
|
-
Callable[P, T],
|
|
211
|
-
],
|
|
212
|
-
Callable[P, T],
|
|
213
|
-
]:
|
|
203
|
+
obj: Callable[P, T] | None = None,
|
|
204
|
+
tags: Iterable[str] | None = None,
|
|
205
|
+
) -> Callable[[Callable[P, T]], Callable[P, T]] | Callable[P, T]:
|
|
214
206
|
"""Decorator for marking a function or method as requiring dependency injection.
|
|
215
207
|
|
|
216
208
|
Args:
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import inspect
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from functools import cached_property
|
|
4
6
|
from typing import Any, Callable, Type, TypeVar, Union
|
|
5
7
|
|
|
6
|
-
from typing_extensions import Annotated, Literal,
|
|
8
|
+
from typing_extensions import Annotated, Literal, Self, TypeAlias
|
|
7
9
|
|
|
8
|
-
from ._utils import get_full_qualname,
|
|
10
|
+
from ._utils import get_full_qualname, get_typed_parameters
|
|
9
11
|
|
|
10
12
|
Scope = Literal["transient", "singleton", "request"]
|
|
11
13
|
|
|
@@ -58,13 +60,13 @@ class Provider:
|
|
|
58
60
|
return get_full_qualname(self.obj)
|
|
59
61
|
|
|
60
62
|
@cached_property
|
|
61
|
-
def parameters(self) ->
|
|
63
|
+
def parameters(self) -> list[inspect.Parameter]:
|
|
62
64
|
"""Returns the parameters of the provider as a mapping.
|
|
63
65
|
|
|
64
66
|
Returns:
|
|
65
67
|
The parameters of the provider.
|
|
66
68
|
"""
|
|
67
|
-
return
|
|
69
|
+
return get_typed_parameters(self.obj)
|
|
68
70
|
|
|
69
71
|
@cached_property
|
|
70
72
|
def is_class(self) -> bool:
|