anydi 0.25.0__tar.gz → 0.25.0a1__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.0 → anydi-0.25.0a1}/PKG-INFO +1 -57
- {anydi-0.25.0 → anydi-0.25.0a1}/README.md +0 -56
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_container.py +49 -39
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_context.py +8 -8
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_module.py +4 -4
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_scanner.py +27 -19
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_types.py +4 -6
- anydi-0.25.0a1/anydi/_utils.py +111 -0
- anydi-0.25.0a1/anydi/ext/django/__init__.py +3 -0
- anydi-0.25.0a1/anydi/ext/django/_utils.py +14 -0
- anydi-0.25.0a1/anydi/ext/django/apps.py +177 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/django/ninja/__init__.py +1 -1
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/django/ninja/_operation.py +0 -2
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/django/ninja/_signature.py +0 -2
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/fastapi.py +2 -2
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/pytest_plugin.py +10 -12
- {anydi-0.25.0 → anydi-0.25.0a1}/pyproject.toml +2 -2
- anydi-0.25.0/anydi/_utils.py +0 -122
- anydi-0.25.0/anydi/ext/django/__init__.py +0 -9
- anydi-0.25.0/anydi/ext/django/_settings.py +0 -39
- anydi-0.25.0/anydi/ext/django/_utils.py +0 -111
- anydi-0.25.0/anydi/ext/django/apps.py +0 -82
- anydi-0.25.0/anydi/ext/django/middleware.py +0 -26
- {anydi-0.25.0 → anydi-0.25.0a1}/LICENSE +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/__init__.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/_logger.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/__init__.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/django/_container.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.25.0 → anydi-0.25.0a1}/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.0a1
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Home-page: https://github.com/antonrh/anydi
|
|
6
6
|
License: MIT
|
|
@@ -139,59 +139,3 @@ 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,59 +98,3 @@ 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,16 +15,21 @@ from typing import (
|
|
|
15
15
|
Awaitable,
|
|
16
16
|
Callable,
|
|
17
17
|
ContextManager,
|
|
18
|
+
Dict,
|
|
18
19
|
Iterable,
|
|
19
20
|
Iterator,
|
|
21
|
+
List,
|
|
20
22
|
Mapping,
|
|
23
|
+
Optional,
|
|
21
24
|
Sequence,
|
|
25
|
+
Type,
|
|
22
26
|
TypeVar,
|
|
27
|
+
Union,
|
|
23
28
|
cast,
|
|
24
29
|
overload,
|
|
25
30
|
)
|
|
26
31
|
|
|
27
|
-
from typing_extensions import ParamSpec, final, get_args, get_origin
|
|
32
|
+
from typing_extensions import Annotated, ParamSpec, final, get_args, get_origin
|
|
28
33
|
|
|
29
34
|
try:
|
|
30
35
|
from types import NoneType
|
|
@@ -43,18 +48,12 @@ from ._logger import logger
|
|
|
43
48
|
from ._module import Module, ModuleRegistry
|
|
44
49
|
from ._scanner import Scanner
|
|
45
50
|
from ._types import AnyInterface, Interface, Provider, Scope, is_marker
|
|
46
|
-
from ._utils import
|
|
47
|
-
get_full_qualname,
|
|
48
|
-
get_typed_parameters,
|
|
49
|
-
get_typed_return_annotation,
|
|
50
|
-
has_resource_origin,
|
|
51
|
-
is_builtin_type,
|
|
52
|
-
)
|
|
51
|
+
from ._utils import get_full_qualname, get_signature, is_builtin_type
|
|
53
52
|
|
|
54
53
|
T = TypeVar("T", bound=Any)
|
|
55
54
|
P = ParamSpec("P")
|
|
56
55
|
|
|
57
|
-
ALLOWED_SCOPES:
|
|
56
|
+
ALLOWED_SCOPES: Dict[Scope, List[Scope]] = {
|
|
58
57
|
"singleton": ["singleton"],
|
|
59
58
|
"request": ["request", "singleton"],
|
|
60
59
|
"transient": ["transient", "singleton", "request"],
|
|
@@ -72,9 +71,10 @@ class Container:
|
|
|
72
71
|
def __init__(
|
|
73
72
|
self,
|
|
74
73
|
*,
|
|
75
|
-
providers: Mapping[
|
|
76
|
-
modules:
|
|
77
|
-
|
|
74
|
+
providers: Optional[Mapping[Type[Any], Provider]] = None,
|
|
75
|
+
modules: Optional[
|
|
76
|
+
Sequence[Union[Module, Type[Module], Callable[[Container], None]]]
|
|
77
|
+
] = None,
|
|
78
78
|
strict: bool = False,
|
|
79
79
|
) -> None:
|
|
80
80
|
"""Initialize the AnyDI instance.
|
|
@@ -84,13 +84,13 @@ class Container:
|
|
|
84
84
|
modules: Optional sequence of modules to register during initialization.
|
|
85
85
|
strict: Whether to enable strict mode. Defaults to False.
|
|
86
86
|
"""
|
|
87
|
-
self._providers:
|
|
87
|
+
self._providers: Dict[Type[Any], Provider] = {}
|
|
88
88
|
self._singleton_context = SingletonContext(self)
|
|
89
89
|
self._transient_context = TransientContext(self)
|
|
90
|
-
self._request_context_var: ContextVar[RequestContext
|
|
90
|
+
self._request_context_var: ContextVar[Optional[RequestContext]] = ContextVar(
|
|
91
91
|
"request_context", default=None
|
|
92
92
|
)
|
|
93
|
-
self._override_instances:
|
|
93
|
+
self._override_instances: Dict[Type[Any], Any] = {}
|
|
94
94
|
self._strict = strict
|
|
95
95
|
|
|
96
96
|
# Components
|
|
@@ -117,7 +117,7 @@ class Container:
|
|
|
117
117
|
return self._strict
|
|
118
118
|
|
|
119
119
|
@property
|
|
120
|
-
def providers(self) ->
|
|
120
|
+
def providers(self) -> Dict[Type[Any], Provider]:
|
|
121
121
|
"""Get the registered providers.
|
|
122
122
|
|
|
123
123
|
Returns:
|
|
@@ -333,7 +333,7 @@ class Container:
|
|
|
333
333
|
"""
|
|
334
334
|
related_providers = []
|
|
335
335
|
|
|
336
|
-
for parameter in provider.parameters:
|
|
336
|
+
for parameter in provider.parameters.values():
|
|
337
337
|
if parameter.annotation is inspect._empty: # noqa
|
|
338
338
|
raise TypeError(
|
|
339
339
|
f"Missing provider `{provider}` "
|
|
@@ -363,7 +363,7 @@ class Container:
|
|
|
363
363
|
"registered with matching scopes."
|
|
364
364
|
)
|
|
365
365
|
|
|
366
|
-
def _detect_scope(self, obj: Callable[..., Any]) -> Scope
|
|
366
|
+
def _detect_scope(self, obj: Callable[..., Any]) -> Optional[Scope]:
|
|
367
367
|
"""Detect the scope for a provider.
|
|
368
368
|
|
|
369
369
|
Args:
|
|
@@ -372,7 +372,7 @@ class Container:
|
|
|
372
372
|
The auto scope, or None if the auto scope cannot be detected.
|
|
373
373
|
"""
|
|
374
374
|
has_transient, has_request, has_singleton = False, False, False
|
|
375
|
-
for parameter in
|
|
375
|
+
for parameter in get_signature(obj).parameters.values():
|
|
376
376
|
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
377
377
|
if not has_transient and sub_provider.scope == "transient":
|
|
378
378
|
has_transient = True
|
|
@@ -389,7 +389,7 @@ class Container:
|
|
|
389
389
|
return None
|
|
390
390
|
|
|
391
391
|
def register_module(
|
|
392
|
-
self, module: Module
|
|
392
|
+
self, module: Union[Module, Type[Module], Callable[[Container], None]]
|
|
393
393
|
) -> None:
|
|
394
394
|
"""Register a module as a callable, module type, or module instance.
|
|
395
395
|
|
|
@@ -633,11 +633,14 @@ class Container:
|
|
|
633
633
|
def inject(self) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
|
634
634
|
|
|
635
635
|
def inject(
|
|
636
|
-
self, obj: Callable[P, T
|
|
637
|
-
) ->
|
|
638
|
-
Callable[
|
|
639
|
-
|
|
640
|
-
|
|
636
|
+
self, obj: Union[Callable[P, Union[T, Awaitable[T]]], None] = None
|
|
637
|
+
) -> Union[
|
|
638
|
+
Callable[
|
|
639
|
+
[Callable[P, Union[T, Awaitable[T]]]],
|
|
640
|
+
Callable[P, Union[T, Awaitable[T]]],
|
|
641
|
+
],
|
|
642
|
+
Callable[P, Union[T, Awaitable[T]]],
|
|
643
|
+
]:
|
|
641
644
|
"""Decorator to inject dependencies into a callable.
|
|
642
645
|
|
|
643
646
|
Args:
|
|
@@ -649,8 +652,8 @@ class Container:
|
|
|
649
652
|
"""
|
|
650
653
|
|
|
651
654
|
def decorator(
|
|
652
|
-
obj: Callable[P, T
|
|
653
|
-
) -> Callable[P, T
|
|
655
|
+
obj: Callable[P, Union[T, Awaitable[T]]],
|
|
656
|
+
) -> Callable[P, Union[T, Awaitable[T]]]:
|
|
654
657
|
injected_params = self._get_injected_params(obj)
|
|
655
658
|
|
|
656
659
|
if inspect.iscoroutinefunction(obj):
|
|
@@ -691,9 +694,12 @@ class Container:
|
|
|
691
694
|
def scan(
|
|
692
695
|
self,
|
|
693
696
|
/,
|
|
694
|
-
packages:
|
|
697
|
+
packages: Union[
|
|
698
|
+
Union[types.ModuleType, str],
|
|
699
|
+
Iterable[Union[types.ModuleType, str]],
|
|
700
|
+
],
|
|
695
701
|
*,
|
|
696
|
-
tags: Iterable[str]
|
|
702
|
+
tags: Optional[Iterable[str]] = None,
|
|
697
703
|
) -> None:
|
|
698
704
|
"""Scan packages or modules for decorated members and inject dependencies.
|
|
699
705
|
|
|
@@ -717,28 +723,32 @@ class Container:
|
|
|
717
723
|
Raises:
|
|
718
724
|
TypeError: If the provider return annotation is missing or invalid.
|
|
719
725
|
"""
|
|
720
|
-
annotation =
|
|
726
|
+
annotation = get_signature(obj).return_annotation
|
|
721
727
|
|
|
722
|
-
if annotation is
|
|
728
|
+
if annotation is inspect._empty: # noqa
|
|
723
729
|
raise TypeError(
|
|
724
730
|
f"Missing `{get_full_qualname(obj)}` provider return annotation."
|
|
725
731
|
)
|
|
726
732
|
|
|
727
|
-
origin = get_origin(annotation)
|
|
733
|
+
origin = get_origin(annotation) or annotation
|
|
734
|
+
args = get_args(annotation)
|
|
728
735
|
|
|
729
|
-
|
|
730
|
-
|
|
736
|
+
# Supported generic types
|
|
737
|
+
if origin in (list, dict, tuple, Annotated):
|
|
731
738
|
if args:
|
|
732
|
-
return
|
|
739
|
+
return annotation
|
|
733
740
|
else:
|
|
734
741
|
raise TypeError(
|
|
735
|
-
f"Cannot use `{get_full_qualname(obj)}`
|
|
742
|
+
f"Cannot use `{get_full_qualname(obj)}` generic type annotation "
|
|
736
743
|
"without actual type."
|
|
737
744
|
)
|
|
738
745
|
|
|
739
|
-
|
|
746
|
+
try:
|
|
747
|
+
return args[0]
|
|
748
|
+
except IndexError:
|
|
749
|
+
return annotation
|
|
740
750
|
|
|
741
|
-
def _get_injected_params(self, obj: Callable[..., Any]) ->
|
|
751
|
+
def _get_injected_params(self, obj: Callable[..., Any]) -> Dict[str, Any]:
|
|
742
752
|
"""Get the injected parameters of a callable object.
|
|
743
753
|
|
|
744
754
|
Args:
|
|
@@ -749,7 +759,7 @@ class Container:
|
|
|
749
759
|
of the injected parameters.
|
|
750
760
|
"""
|
|
751
761
|
injected_params = {}
|
|
752
|
-
for parameter in
|
|
762
|
+
for parameter in get_signature(obj).parameters.values():
|
|
753
763
|
if not is_marker(parameter.default):
|
|
754
764
|
continue
|
|
755
765
|
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, TypeVar, cast
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, 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.values():
|
|
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.values():
|
|
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, TypeVar
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Tuple, Type, TypeVar, Union
|
|
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: Module
|
|
70
|
+
self, module: Union[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,6 +10,8 @@ from typing import (
|
|
|
10
10
|
Any,
|
|
11
11
|
Callable,
|
|
12
12
|
Iterable,
|
|
13
|
+
List,
|
|
14
|
+
Optional,
|
|
13
15
|
TypeVar,
|
|
14
16
|
Union,
|
|
15
17
|
cast,
|
|
@@ -20,7 +22,7 @@ from typing import (
|
|
|
20
22
|
from typing_extensions import NamedTuple, ParamSpec
|
|
21
23
|
|
|
22
24
|
from ._types import is_marker
|
|
23
|
-
from ._utils import
|
|
25
|
+
from ._utils import get_signature
|
|
24
26
|
|
|
25
27
|
if TYPE_CHECKING:
|
|
26
28
|
from ._container import Container
|
|
@@ -54,9 +56,9 @@ class Scanner:
|
|
|
54
56
|
def scan(
|
|
55
57
|
self,
|
|
56
58
|
/,
|
|
57
|
-
packages: ModuleType
|
|
59
|
+
packages: Union[Union[ModuleType, str], Iterable[Union[ModuleType, str]]],
|
|
58
60
|
*,
|
|
59
|
-
tags: Iterable[str]
|
|
61
|
+
tags: Optional[Iterable[str]] = None,
|
|
60
62
|
) -> None:
|
|
61
63
|
"""Scan packages or modules for decorated members and inject dependencies.
|
|
62
64
|
|
|
@@ -66,10 +68,10 @@ class Scanner:
|
|
|
66
68
|
tags: Optional list of tags to filter the scanned members. Only members
|
|
67
69
|
with at least one matching tag will be scanned. Defaults to None.
|
|
68
70
|
"""
|
|
69
|
-
dependencies:
|
|
71
|
+
dependencies: List[Dependency] = []
|
|
70
72
|
|
|
71
73
|
if isinstance(packages, Iterable) and not isinstance(packages, str):
|
|
72
|
-
scan_packages: Iterable[ModuleType
|
|
74
|
+
scan_packages: Iterable[Union[ModuleType, str]] = packages
|
|
73
75
|
else:
|
|
74
76
|
scan_packages = cast(Iterable[Union[ModuleType, str]], [packages])
|
|
75
77
|
|
|
@@ -82,10 +84,10 @@ class Scanner:
|
|
|
82
84
|
|
|
83
85
|
def _scan_package(
|
|
84
86
|
self,
|
|
85
|
-
package: ModuleType
|
|
87
|
+
package: Union[ModuleType, str],
|
|
86
88
|
*,
|
|
87
|
-
tags: Iterable[str]
|
|
88
|
-
) ->
|
|
89
|
+
tags: Optional[Iterable[str]] = None,
|
|
90
|
+
) -> List[Dependency]:
|
|
89
91
|
"""Scan a package or module for decorated members.
|
|
90
92
|
|
|
91
93
|
Args:
|
|
@@ -105,7 +107,7 @@ class Scanner:
|
|
|
105
107
|
if not package_path:
|
|
106
108
|
return self._scan_module(package, tags=tags)
|
|
107
109
|
|
|
108
|
-
dependencies:
|
|
110
|
+
dependencies: List[Dependency] = []
|
|
109
111
|
|
|
110
112
|
for module_info in pkgutil.walk_packages(
|
|
111
113
|
path=package_path, prefix=package.__name__ + "."
|
|
@@ -117,7 +119,7 @@ class Scanner:
|
|
|
117
119
|
|
|
118
120
|
def _scan_module(
|
|
119
121
|
self, module: ModuleType, *, tags: Iterable[str]
|
|
120
|
-
) ->
|
|
122
|
+
) -> List[Dependency]:
|
|
121
123
|
"""Scan a module for decorated members.
|
|
122
124
|
|
|
123
125
|
Args:
|
|
@@ -128,7 +130,7 @@ class Scanner:
|
|
|
128
130
|
Returns:
|
|
129
131
|
A list of scanned dependencies.
|
|
130
132
|
"""
|
|
131
|
-
dependencies:
|
|
133
|
+
dependencies: List[Dependency] = []
|
|
132
134
|
|
|
133
135
|
for _, member in inspect.getmembers(module):
|
|
134
136
|
if getattr(member, "__module__", None) != module.__name__ or not callable(
|
|
@@ -157,10 +159,10 @@ class Scanner:
|
|
|
157
159
|
|
|
158
160
|
# Get by Marker
|
|
159
161
|
if inspect.isclass(member):
|
|
160
|
-
|
|
162
|
+
signature = get_signature(member.__init__)
|
|
161
163
|
else:
|
|
162
|
-
|
|
163
|
-
for parameter in parameters:
|
|
164
|
+
signature = get_signature(member)
|
|
165
|
+
for parameter in signature.parameters.values():
|
|
164
166
|
if is_marker(parameter.default):
|
|
165
167
|
dependencies.append(
|
|
166
168
|
self._create_dependency(member=member, module=module)
|
|
@@ -186,7 +188,7 @@ class Scanner:
|
|
|
186
188
|
|
|
187
189
|
class InjectDecoratorArgs(NamedTuple):
|
|
188
190
|
wrapped: bool
|
|
189
|
-
tags: Iterable[str]
|
|
191
|
+
tags: Optional[Iterable[str]]
|
|
190
192
|
|
|
191
193
|
|
|
192
194
|
@overload
|
|
@@ -195,14 +197,20 @@ def injectable(obj: Callable[P, T]) -> Callable[P, T]: ...
|
|
|
195
197
|
|
|
196
198
|
@overload
|
|
197
199
|
def injectable(
|
|
198
|
-
*, tags: Iterable[str]
|
|
200
|
+
*, tags: Optional[Iterable[str]] = None
|
|
199
201
|
) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
|
200
202
|
|
|
201
203
|
|
|
202
204
|
def injectable(
|
|
203
|
-
obj: Callable[P, T]
|
|
204
|
-
tags: Iterable[str]
|
|
205
|
-
) ->
|
|
205
|
+
obj: Optional[Callable[P, T]] = None,
|
|
206
|
+
tags: Optional[Iterable[str]] = None,
|
|
207
|
+
) -> Union[
|
|
208
|
+
Callable[
|
|
209
|
+
[Callable[P, T]],
|
|
210
|
+
Callable[P, T],
|
|
211
|
+
],
|
|
212
|
+
Callable[P, T],
|
|
213
|
+
]:
|
|
206
214
|
"""Decorator for marking a function or method as requiring dependency injection.
|
|
207
215
|
|
|
208
216
|
Args:
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import inspect
|
|
4
2
|
from dataclasses import dataclass
|
|
5
3
|
from functools import cached_property
|
|
6
4
|
from typing import Any, Callable, Type, TypeVar, Union
|
|
7
5
|
|
|
8
|
-
from typing_extensions import Annotated, Literal, Self, TypeAlias
|
|
6
|
+
from typing_extensions import Annotated, Literal, Mapping, Self, TypeAlias
|
|
9
7
|
|
|
10
|
-
from ._utils import get_full_qualname,
|
|
8
|
+
from ._utils import get_full_qualname, get_signature
|
|
11
9
|
|
|
12
10
|
Scope = Literal["transient", "singleton", "request"]
|
|
13
11
|
|
|
@@ -60,13 +58,13 @@ class Provider:
|
|
|
60
58
|
return get_full_qualname(self.obj)
|
|
61
59
|
|
|
62
60
|
@cached_property
|
|
63
|
-
def parameters(self) ->
|
|
61
|
+
def parameters(self) -> Mapping[str, inspect.Parameter]:
|
|
64
62
|
"""Returns the parameters of the provider as a mapping.
|
|
65
63
|
|
|
66
64
|
Returns:
|
|
67
65
|
The parameters of the provider.
|
|
68
66
|
"""
|
|
69
|
-
return
|
|
67
|
+
return get_signature(self.obj).parameters
|
|
70
68
|
|
|
71
69
|
@cached_property
|
|
72
70
|
def is_class(self) -> bool:
|