anydi 0.32.2__py3-none-any.whl → 0.33.1__py3-none-any.whl
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/_container.py +133 -17
- anydi/_context.py +47 -23
- anydi/_provider.py +1 -1
- anydi/_types.py +12 -0
- anydi/ext/_utils.py +2 -2
- anydi/ext/fastapi.py +1 -1
- anydi/ext/faststream.py +1 -1
- anydi/ext/pytest_plugin.py +8 -4
- {anydi-0.32.2.dist-info → anydi-0.33.1.dist-info}/METADATA +1 -1
- {anydi-0.32.2.dist-info → anydi-0.33.1.dist-info}/RECORD +13 -14
- anydi/_injector.py +0 -94
- {anydi-0.32.2.dist-info → anydi-0.33.1.dist-info}/LICENSE +0 -0
- {anydi-0.32.2.dist-info → anydi-0.33.1.dist-info}/WHEEL +0 -0
- {anydi-0.32.2.dist-info → anydi-0.33.1.dist-info}/entry_points.txt +0 -0
anydi/_container.py
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
|
+
import functools
|
|
6
7
|
import inspect
|
|
7
8
|
import types
|
|
8
9
|
from collections import defaultdict
|
|
9
|
-
from collections.abc import AsyncIterator,
|
|
10
|
+
from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
|
|
10
11
|
from contextvars import ContextVar
|
|
11
12
|
from typing import Any, Callable, TypeVar, cast, overload
|
|
13
|
+
from weakref import WeakKeyDictionary
|
|
12
14
|
|
|
13
15
|
from typing_extensions import ParamSpec, Self, final
|
|
14
16
|
|
|
@@ -19,11 +21,11 @@ from ._context import (
|
|
|
19
21
|
SingletonContext,
|
|
20
22
|
TransientContext,
|
|
21
23
|
)
|
|
22
|
-
from .
|
|
24
|
+
from ._logger import logger
|
|
23
25
|
from ._module import Module, ModuleRegistry
|
|
24
26
|
from ._provider import Provider
|
|
25
27
|
from ._scanner import Scanner
|
|
26
|
-
from ._types import AnyInterface, Interface, Scope
|
|
28
|
+
from ._types import AnyInterface, DependencyWrapper, Interface, Scope, is_marker
|
|
27
29
|
from ._utils import get_full_qualname, get_typed_parameters, is_builtin_type
|
|
28
30
|
|
|
29
31
|
T = TypeVar("T", bound=Any)
|
|
@@ -47,6 +49,7 @@ class Container:
|
|
|
47
49
|
modules: Sequence[Module | type[Module] | Callable[[Container], None] | str]
|
|
48
50
|
| None = None,
|
|
49
51
|
strict: bool = False,
|
|
52
|
+
testing: bool = False,
|
|
50
53
|
) -> None:
|
|
51
54
|
self._providers: dict[type[Any], Provider] = {}
|
|
52
55
|
self._resource_cache: dict[Scope, list[type[Any]]] = defaultdict(list)
|
|
@@ -57,10 +60,13 @@ class Container:
|
|
|
57
60
|
)
|
|
58
61
|
self._override_instances: dict[type[Any], Any] = {}
|
|
59
62
|
self._strict = strict
|
|
63
|
+
self._testing = testing
|
|
60
64
|
self._unresolved_interfaces: set[type[Any]] = set()
|
|
65
|
+
self._inject_cache: WeakKeyDictionary[
|
|
66
|
+
Callable[..., Any], Callable[..., Any]
|
|
67
|
+
] = WeakKeyDictionary()
|
|
61
68
|
|
|
62
69
|
# Components
|
|
63
|
-
self._injector = Injector(self)
|
|
64
70
|
self._modules = ModuleRegistry(self)
|
|
65
71
|
self._scanner = Scanner(self)
|
|
66
72
|
|
|
@@ -79,6 +85,11 @@ class Container:
|
|
|
79
85
|
"""Check if strict mode is enabled."""
|
|
80
86
|
return self._strict
|
|
81
87
|
|
|
88
|
+
@property
|
|
89
|
+
def testing(self) -> bool:
|
|
90
|
+
"""Check if testing mode is enabled."""
|
|
91
|
+
return self._testing
|
|
92
|
+
|
|
82
93
|
@property
|
|
83
94
|
def providers(self) -> dict[type[Any], Provider]:
|
|
84
95
|
"""Get the registered providers."""
|
|
@@ -202,6 +213,10 @@ class Container:
|
|
|
202
213
|
annotation, parent_scope=provider.scope
|
|
203
214
|
)
|
|
204
215
|
except LookupError:
|
|
216
|
+
# Skip unresolved interfaces in non-strict mode
|
|
217
|
+
if not self.strict and parameter.default is not inspect.Parameter.empty:
|
|
218
|
+
continue
|
|
219
|
+
|
|
205
220
|
if provider.scope not in {"singleton", "transient"}:
|
|
206
221
|
self._unresolved_interfaces.add(provider.interface)
|
|
207
222
|
continue
|
|
@@ -225,7 +240,12 @@ class Container:
|
|
|
225
240
|
scopes = set()
|
|
226
241
|
|
|
227
242
|
for parameter in get_typed_parameters(call):
|
|
228
|
-
|
|
243
|
+
try:
|
|
244
|
+
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
245
|
+
except LookupError:
|
|
246
|
+
if not self.strict and parameter.default is not inspect.Parameter.empty:
|
|
247
|
+
continue
|
|
248
|
+
raise
|
|
229
249
|
scope = sub_provider.scope
|
|
230
250
|
|
|
231
251
|
if scope == "transient":
|
|
@@ -234,7 +254,7 @@ class Container:
|
|
|
234
254
|
|
|
235
255
|
# If all scopes are found, we can return based on priority order
|
|
236
256
|
if {"transient", "request", "singleton"}.issubset(scopes):
|
|
237
|
-
break
|
|
257
|
+
break # pragma: no cover
|
|
238
258
|
|
|
239
259
|
# Determine scope based on priority
|
|
240
260
|
if "request" in scopes:
|
|
@@ -346,7 +366,10 @@ class Container:
|
|
|
346
366
|
|
|
347
367
|
provider = self._get_or_register_provider(interface)
|
|
348
368
|
scoped_context = self._get_scoped_context(provider.scope)
|
|
349
|
-
|
|
369
|
+
instance, created = scoped_context.get_or_create(provider)
|
|
370
|
+
if self.testing and created:
|
|
371
|
+
self._patch_test_resolver(instance)
|
|
372
|
+
return cast(T, instance)
|
|
350
373
|
|
|
351
374
|
@overload
|
|
352
375
|
async def aresolve(self, interface: Interface[T]) -> T: ...
|
|
@@ -361,7 +384,33 @@ class Container:
|
|
|
361
384
|
|
|
362
385
|
provider = self._get_or_register_provider(interface)
|
|
363
386
|
scoped_context = self._get_scoped_context(provider.scope)
|
|
364
|
-
|
|
387
|
+
instance, created = await scoped_context.aget_or_create(provider)
|
|
388
|
+
if self.testing and created:
|
|
389
|
+
self._patch_test_resolver(instance)
|
|
390
|
+
return cast(T, instance)
|
|
391
|
+
|
|
392
|
+
def _patch_test_resolver(self, instance: Any) -> None:
|
|
393
|
+
"""Patch the test resolver for the instance."""
|
|
394
|
+
if not hasattr(instance, "__dict__"):
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
wrapped = {
|
|
398
|
+
name: value.interface
|
|
399
|
+
for name, value in instance.__dict__.items()
|
|
400
|
+
if isinstance(value, DependencyWrapper)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# Custom resolver function
|
|
404
|
+
def _resolver(_self: Any, _name: str) -> Any:
|
|
405
|
+
if _name in wrapped:
|
|
406
|
+
# Resolve the dependency if it's wrapped
|
|
407
|
+
return self.resolve(wrapped[_name])
|
|
408
|
+
# Fall back to default behavior
|
|
409
|
+
return object.__getattribute__(_self, _name)
|
|
410
|
+
|
|
411
|
+
# Apply the patched resolver if wrapped attributes exist
|
|
412
|
+
if wrapped:
|
|
413
|
+
instance.__class__.__getattribute__ = _resolver
|
|
365
414
|
|
|
366
415
|
def is_resolved(self, interface: AnyInterface) -> bool:
|
|
367
416
|
"""Check if an instance by interface exists."""
|
|
@@ -424,25 +473,92 @@ class Container:
|
|
|
424
473
|
def inject(self) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
|
425
474
|
|
|
426
475
|
def inject(
|
|
427
|
-
self, func: Callable[P, T
|
|
428
|
-
) ->
|
|
429
|
-
Callable[[Callable[P, T | Awaitable[T]]], Callable[P, T | Awaitable[T]]]
|
|
430
|
-
| Callable[P, T | Awaitable[T]]
|
|
431
|
-
):
|
|
476
|
+
self, func: Callable[P, T] | None = None
|
|
477
|
+
) -> Callable[[Callable[P, T]], Callable[P, T]] | Callable[P, T]:
|
|
432
478
|
"""Decorator to inject dependencies into a callable."""
|
|
433
479
|
|
|
434
480
|
def decorator(
|
|
435
|
-
|
|
436
|
-
) -> Callable[P, T
|
|
437
|
-
return self.
|
|
481
|
+
call: Callable[P, T],
|
|
482
|
+
) -> Callable[P, T]:
|
|
483
|
+
return self._inject(call)
|
|
438
484
|
|
|
439
485
|
if func is None:
|
|
440
486
|
return decorator
|
|
441
487
|
return decorator(func)
|
|
442
488
|
|
|
489
|
+
def _inject(
|
|
490
|
+
self,
|
|
491
|
+
call: Callable[P, T],
|
|
492
|
+
) -> Callable[P, T]:
|
|
493
|
+
"""Inject dependencies into a callable."""
|
|
494
|
+
if call in self._inject_cache:
|
|
495
|
+
return cast(Callable[P, T], self._inject_cache[call])
|
|
496
|
+
|
|
497
|
+
injected_params = self._get_injected_params(call)
|
|
498
|
+
|
|
499
|
+
if inspect.iscoroutinefunction(call):
|
|
500
|
+
|
|
501
|
+
@functools.wraps(call)
|
|
502
|
+
async def awrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
503
|
+
for name, annotation in injected_params.items():
|
|
504
|
+
kwargs[name] = await self.aresolve(annotation)
|
|
505
|
+
return cast(T, await call(*args, **kwargs))
|
|
506
|
+
|
|
507
|
+
self._inject_cache[call] = awrapper
|
|
508
|
+
|
|
509
|
+
return awrapper # type: ignore[return-value]
|
|
510
|
+
|
|
511
|
+
@functools.wraps(call)
|
|
512
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
513
|
+
for name, annotation in injected_params.items():
|
|
514
|
+
kwargs[name] = self.resolve(annotation)
|
|
515
|
+
return call(*args, **kwargs)
|
|
516
|
+
|
|
517
|
+
self._inject_cache[call] = wrapper
|
|
518
|
+
|
|
519
|
+
return wrapper
|
|
520
|
+
|
|
521
|
+
def _get_injected_params(self, call: Callable[..., Any]) -> dict[str, Any]:
|
|
522
|
+
"""Get the injected parameters of a callable object."""
|
|
523
|
+
injected_params = {}
|
|
524
|
+
for parameter in get_typed_parameters(call):
|
|
525
|
+
if not is_marker(parameter.default):
|
|
526
|
+
continue
|
|
527
|
+
try:
|
|
528
|
+
self._validate_injected_parameter(call, parameter)
|
|
529
|
+
except LookupError as exc:
|
|
530
|
+
if not self.strict:
|
|
531
|
+
logger.debug(
|
|
532
|
+
f"Cannot validate the `{get_full_qualname(call)}` parameter "
|
|
533
|
+
f"`{parameter.name}` with an annotation of "
|
|
534
|
+
f"`{get_full_qualname(parameter.annotation)} due to being "
|
|
535
|
+
"in non-strict mode. It will be validated at the first call."
|
|
536
|
+
)
|
|
537
|
+
else:
|
|
538
|
+
raise exc
|
|
539
|
+
injected_params[parameter.name] = parameter.annotation
|
|
540
|
+
return injected_params
|
|
541
|
+
|
|
542
|
+
def _validate_injected_parameter(
|
|
543
|
+
self, call: Callable[..., Any], parameter: inspect.Parameter
|
|
544
|
+
) -> None:
|
|
545
|
+
"""Validate an injected parameter."""
|
|
546
|
+
if parameter.annotation is inspect.Parameter.empty:
|
|
547
|
+
raise TypeError(
|
|
548
|
+
f"Missing `{get_full_qualname(call)}` parameter "
|
|
549
|
+
f"`{parameter.name}` annotation."
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
if not self.is_registered(parameter.annotation):
|
|
553
|
+
raise LookupError(
|
|
554
|
+
f"`{get_full_qualname(call)}` has an unknown dependency parameter "
|
|
555
|
+
f"`{parameter.name}` with an annotation of "
|
|
556
|
+
f"`{get_full_qualname(parameter.annotation)}`."
|
|
557
|
+
)
|
|
558
|
+
|
|
443
559
|
def run(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
444
560
|
"""Run the given function with injected dependencies."""
|
|
445
|
-
return
|
|
561
|
+
return self._inject(func)(*args, **kwargs)
|
|
446
562
|
|
|
447
563
|
def scan(
|
|
448
564
|
self,
|
anydi/_context.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Callable, ClassVar
|
|
|
9
9
|
from typing_extensions import Self, final
|
|
10
10
|
|
|
11
11
|
from ._provider import CallableKind, Provider
|
|
12
|
-
from ._types import AnyInterface, Scope, is_event_type
|
|
12
|
+
from ._types import AnyInterface, DependencyWrapper, Scope, is_event_type
|
|
13
13
|
from ._utils import get_full_qualname, run_async
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
@@ -30,12 +30,12 @@ class ScopedContext(abc.ABC):
|
|
|
30
30
|
self._instances[interface] = instance
|
|
31
31
|
|
|
32
32
|
@abc.abstractmethod
|
|
33
|
-
def
|
|
34
|
-
"""Get an instance of a dependency from the scoped context."""
|
|
33
|
+
def get_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
34
|
+
"""Get or create an instance of a dependency from the scoped context."""
|
|
35
35
|
|
|
36
36
|
@abc.abstractmethod
|
|
37
|
-
async def
|
|
38
|
-
"""Get an async instance of a dependency from the scoped context."""
|
|
37
|
+
async def aget_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
38
|
+
"""Get or create an async instance of a dependency from the scoped context."""
|
|
39
39
|
|
|
40
40
|
def _create_instance(self, provider: Provider) -> Any:
|
|
41
41
|
"""Create an instance using the provider."""
|
|
@@ -44,12 +44,12 @@ class ScopedContext(abc.ABC):
|
|
|
44
44
|
f"The instance for the coroutine provider `{provider}` cannot be "
|
|
45
45
|
"created in synchronous mode."
|
|
46
46
|
)
|
|
47
|
-
args, kwargs = self.
|
|
47
|
+
args, kwargs = self._get_provided_args(provider)
|
|
48
48
|
return provider.call(*args, **kwargs)
|
|
49
49
|
|
|
50
50
|
async def _acreate_instance(self, provider: Provider) -> Any:
|
|
51
51
|
"""Create an instance asynchronously using the provider."""
|
|
52
|
-
args, kwargs = await self.
|
|
52
|
+
args, kwargs = await self._aget_provided_args(provider)
|
|
53
53
|
if provider.kind == CallableKind.COROUTINE:
|
|
54
54
|
return await provider.call(*args, **kwargs)
|
|
55
55
|
return await run_async(provider.call, *args, **kwargs)
|
|
@@ -78,7 +78,7 @@ class ScopedContext(abc.ABC):
|
|
|
78
78
|
"or set in the scoped context."
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
def
|
|
81
|
+
def _get_provided_args(
|
|
82
82
|
self, provider: Provider
|
|
83
83
|
) -> tuple[list[Any], dict[str, Any]]:
|
|
84
84
|
"""Retrieve the arguments for a provider."""
|
|
@@ -91,14 +91,24 @@ class ScopedContext(abc.ABC):
|
|
|
91
91
|
elif parameter.annotation in self._instances:
|
|
92
92
|
instance = self._instances[parameter.annotation]
|
|
93
93
|
else:
|
|
94
|
-
|
|
94
|
+
try:
|
|
95
|
+
instance = self._resolve_parameter(provider, parameter)
|
|
96
|
+
except LookupError:
|
|
97
|
+
if parameter.default is inspect.Parameter.empty:
|
|
98
|
+
raise
|
|
99
|
+
instance = parameter.default
|
|
100
|
+
else:
|
|
101
|
+
if self.container.testing:
|
|
102
|
+
instance = DependencyWrapper(
|
|
103
|
+
interface=parameter.annotation, instance=instance
|
|
104
|
+
)
|
|
95
105
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
96
106
|
args.append(instance)
|
|
97
107
|
else:
|
|
98
108
|
kwargs[parameter.name] = instance
|
|
99
109
|
return args, kwargs
|
|
100
110
|
|
|
101
|
-
async def
|
|
111
|
+
async def _aget_provided_args(
|
|
102
112
|
self, provider: Provider
|
|
103
113
|
) -> tuple[list[Any], dict[str, Any]]:
|
|
104
114
|
"""Asynchronously retrieve the arguments for a provider."""
|
|
@@ -111,7 +121,17 @@ class ScopedContext(abc.ABC):
|
|
|
111
121
|
elif parameter.annotation in self._instances:
|
|
112
122
|
instance = self._instances[parameter.annotation]
|
|
113
123
|
else:
|
|
114
|
-
|
|
124
|
+
try:
|
|
125
|
+
instance = await self._aresolve_parameter(provider, parameter)
|
|
126
|
+
except LookupError:
|
|
127
|
+
if parameter.default is inspect.Parameter.empty:
|
|
128
|
+
raise
|
|
129
|
+
instance = parameter.default
|
|
130
|
+
else:
|
|
131
|
+
if self.container.testing:
|
|
132
|
+
instance = DependencyWrapper(
|
|
133
|
+
interface=parameter.annotation, instance=instance
|
|
134
|
+
)
|
|
115
135
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
116
136
|
args.append(instance)
|
|
117
137
|
else:
|
|
@@ -128,7 +148,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
128
148
|
self._stack = contextlib.ExitStack()
|
|
129
149
|
self._async_stack = contextlib.AsyncExitStack()
|
|
130
150
|
|
|
131
|
-
def
|
|
151
|
+
def get_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
132
152
|
"""Get an instance of a dependency from the scoped context."""
|
|
133
153
|
instance = self._instances.get(provider.interface)
|
|
134
154
|
if instance is None:
|
|
@@ -143,9 +163,10 @@ class ResourceScopedContext(ScopedContext):
|
|
|
143
163
|
else:
|
|
144
164
|
instance = self._create_instance(provider)
|
|
145
165
|
self._instances[provider.interface] = instance
|
|
146
|
-
|
|
166
|
+
return instance, True
|
|
167
|
+
return instance, False
|
|
147
168
|
|
|
148
|
-
async def
|
|
169
|
+
async def aget_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
149
170
|
"""Get an async instance of a dependency from the scoped context."""
|
|
150
171
|
instance = self._instances.get(provider.interface)
|
|
151
172
|
if instance is None:
|
|
@@ -156,7 +177,8 @@ class ResourceScopedContext(ScopedContext):
|
|
|
156
177
|
else:
|
|
157
178
|
instance = await self._acreate_instance(provider)
|
|
158
179
|
self._instances[provider.interface] = instance
|
|
159
|
-
|
|
180
|
+
return instance, True
|
|
181
|
+
return instance, False
|
|
160
182
|
|
|
161
183
|
def has(self, interface: AnyInterface) -> bool:
|
|
162
184
|
"""Check if the scoped context has an instance of the dependency."""
|
|
@@ -172,7 +194,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
172
194
|
|
|
173
195
|
def _create_resource(self, provider: Provider) -> Any:
|
|
174
196
|
"""Create a resource using the provider."""
|
|
175
|
-
args, kwargs = self.
|
|
197
|
+
args, kwargs = self._get_provided_args(provider)
|
|
176
198
|
cm = contextlib.contextmanager(provider.call)(*args, **kwargs)
|
|
177
199
|
return self._stack.enter_context(cm)
|
|
178
200
|
|
|
@@ -186,7 +208,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
186
208
|
|
|
187
209
|
async def _acreate_resource(self, provider: Provider) -> Any:
|
|
188
210
|
"""Create a resource asynchronously using the provider."""
|
|
189
|
-
args, kwargs = await self.
|
|
211
|
+
args, kwargs = await self._aget_provided_args(provider)
|
|
190
212
|
cm = contextlib.asynccontextmanager(provider.call)(*args, **kwargs)
|
|
191
213
|
return await self._async_stack.enter_async_context(cm)
|
|
192
214
|
|
|
@@ -287,10 +309,12 @@ class TransientContext(ScopedContext):
|
|
|
287
309
|
|
|
288
310
|
scope = "transient"
|
|
289
311
|
|
|
290
|
-
def
|
|
291
|
-
"""Get an instance of a dependency from the transient context."""
|
|
292
|
-
return self._create_instance(provider)
|
|
312
|
+
def get_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
313
|
+
"""Get or create an instance of a dependency from the transient context."""
|
|
314
|
+
return self._create_instance(provider), True
|
|
293
315
|
|
|
294
|
-
async def
|
|
295
|
-
"""
|
|
296
|
-
|
|
316
|
+
async def aget_or_create(self, provider: Provider) -> tuple[Any, bool]:
|
|
317
|
+
"""
|
|
318
|
+
Get or create an async instance of a dependency from the transient context.
|
|
319
|
+
"""
|
|
320
|
+
return await self._acreate_instance(provider), True
|
anydi/_provider.py
CHANGED
anydi/_types.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from typing import Annotated, Any, TypeVar, Union
|
|
5
6
|
|
|
6
7
|
from typing_extensions import Literal, Self, TypeAlias
|
|
@@ -35,3 +36,14 @@ class Event:
|
|
|
35
36
|
def is_event_type(obj: Any) -> bool:
|
|
36
37
|
"""Checks if an object is an event type."""
|
|
37
38
|
return inspect.isclass(obj) and issubclass(obj, Event)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class DependencyWrapper:
|
|
43
|
+
interface: type[Any]
|
|
44
|
+
instance: Any
|
|
45
|
+
|
|
46
|
+
def __getattribute__(self, name: str) -> Any:
|
|
47
|
+
if name in {"interface", "instance"}:
|
|
48
|
+
return object.__getattribute__(self, name)
|
|
49
|
+
return getattr(self.instance, name)
|
anydi/ext/_utils.py
CHANGED
|
@@ -61,7 +61,7 @@ def patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def patch_call_parameter(
|
|
64
|
-
call: Callable[..., Any], parameter: inspect.Parameter
|
|
64
|
+
container: Container, call: Callable[..., Any], parameter: inspect.Parameter
|
|
65
65
|
) -> None:
|
|
66
66
|
"""Patch a parameter to inject dependencies using AnyDI."""
|
|
67
67
|
parameter = patch_annotated_parameter(parameter)
|
|
@@ -78,6 +78,6 @@ def patch_call_parameter(
|
|
|
78
78
|
"first call because it is running in non-strict mode."
|
|
79
79
|
)
|
|
80
80
|
else:
|
|
81
|
-
container.
|
|
81
|
+
container._validate_injected_parameter(call, parameter) # noqa
|
|
82
82
|
|
|
83
83
|
parameter.default.interface = parameter.annotation
|
anydi/ext/fastapi.py
CHANGED
|
@@ -41,7 +41,7 @@ def install(app: FastAPI, container: Container) -> None:
|
|
|
41
41
|
if not call:
|
|
42
42
|
continue # pragma: no cover
|
|
43
43
|
for parameter in get_typed_parameters(call):
|
|
44
|
-
patch_call_parameter(call, parameter
|
|
44
|
+
patch_call_parameter(container, call, parameter)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def get_container(request: Request) -> Container:
|
anydi/ext/faststream.py
CHANGED
|
@@ -26,7 +26,7 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
|
26
26
|
for handler in _get_broken_handlers(broker):
|
|
27
27
|
call = handler._original_call # noqa
|
|
28
28
|
for parameter in get_typed_parameters(call):
|
|
29
|
-
patch_call_parameter(call, parameter
|
|
29
|
+
patch_call_parameter(container, call, parameter)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
anydi/ext/pytest_plugin.py
CHANGED
|
@@ -105,8 +105,10 @@ def _anydi_inject(
|
|
|
105
105
|
|
|
106
106
|
try:
|
|
107
107
|
request.node.funcargs[argname] = container.resolve(interface)
|
|
108
|
-
except Exception:
|
|
109
|
-
logger.warning(
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
logger.warning(
|
|
110
|
+
f"Failed to resolve dependency for argument '{argname}'.", exc_info=exc
|
|
111
|
+
)
|
|
110
112
|
_anydi_unresolved.append(interface)
|
|
111
113
|
|
|
112
114
|
|
|
@@ -131,6 +133,8 @@ async def _anydi_ainject(
|
|
|
131
133
|
|
|
132
134
|
try:
|
|
133
135
|
request.node.funcargs[argname] = await container.aresolve(interface)
|
|
134
|
-
except Exception:
|
|
135
|
-
logger.warning(
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"Failed to resolve dependency for argument '{argname}'.", exc_info=exc
|
|
139
|
+
)
|
|
136
140
|
_anydi_unresolved.append(interface)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
anydi/__init__.py,sha256=EsR-HiMe8cWS9PQbY23ibc91STK1WTn02DFMPV-TNU4,509
|
|
2
|
-
anydi/_container.py,sha256=
|
|
3
|
-
anydi/_context.py,sha256=
|
|
4
|
-
anydi/_injector.py,sha256=GdI4NdcB72sJPyBFh9ZKyPflEs-6HaUbiHN2H5QIgHY,3454
|
|
2
|
+
anydi/_container.py,sha256=shefN3SuhmPBzU6gxV-YRGJlz-m8Nv5RJ-T93veSxgE,21389
|
|
3
|
+
anydi/_context.py,sha256=6Veqt15Tp4DfOzppUOIBaymosicYUWMeSa1b7ZAdtxw,12866
|
|
5
4
|
anydi/_logger.py,sha256=UpubJUnW83kffFxkhUlObm2DmZX1Pjqoz9YFKS-JOPg,52
|
|
6
5
|
anydi/_module.py,sha256=cgojC7Z7oMtsUnkfSc65cRYOfZ8Q6KwjusNJzx_VSbk,2729
|
|
7
|
-
anydi/_provider.py,sha256
|
|
6
|
+
anydi/_provider.py,sha256=w_GnRo324aqNORRJwuURexA54c1M3smj34Q8EaV0QGE,6213
|
|
8
7
|
anydi/_scanner.py,sha256=F2sHgJvkRYXYnu4F5iSrnIPVzwnNeS7tRPXziirh4NI,4898
|
|
9
|
-
anydi/_types.py,sha256=
|
|
8
|
+
anydi/_types.py,sha256=90xdbH2NrFXbridFf9mjOknhcXMW5L0jm92zP_LvKrg,1120
|
|
10
9
|
anydi/_utils.py,sha256=guw4sFCvsisJmneKWlZi18YDYll_CjlO_f2cH97rDFQ,3280
|
|
11
10
|
anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
anydi/ext/_utils.py,sha256=
|
|
11
|
+
anydi/ext/_utils.py,sha256=U6sRqWzccWUu7eMhbXX1NrwcaxitQF9cO1KxnKF37gw,2566
|
|
13
12
|
anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
|
|
14
13
|
anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
|
|
15
14
|
anydi/ext/django/_settings.py,sha256=Z0RlAuXoO73oahWeMkK10w8c-4uCBde-DBpeKTV5USY,853
|
|
@@ -19,15 +18,15 @@ anydi/ext/django/middleware.py,sha256=tryIaBVmfPmilGrnKpLNlLCOPN5rw4_pXY3ojFXv3O
|
|
|
19
18
|
anydi/ext/django/ninja/__init__.py,sha256=kW3grUgWp_nkWSG_-39ADHMrZLGNcj9TsJ9OW8iWWrk,546
|
|
20
19
|
anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI8W-oO2w,2696
|
|
21
20
|
anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfyk_NVEWw,2207
|
|
22
|
-
anydi/ext/fastapi.py,sha256=
|
|
23
|
-
anydi/ext/faststream.py,sha256=
|
|
21
|
+
anydi/ext/fastapi.py,sha256=AEL3ubu-LxUPHMMt1YIn3En_JZC7nyBKmKxmhka3O3c,2436
|
|
22
|
+
anydi/ext/faststream.py,sha256=qXnNGvAqWWp9kbhbQUE6EF_OPUiYQmtOH211_O7BI_0,1898
|
|
24
23
|
anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
|
|
25
|
-
anydi/ext/pytest_plugin.py,sha256=
|
|
24
|
+
anydi/ext/pytest_plugin.py,sha256=3x_ZYFcLp4ZCRrs7neoohmWz56O9ydm92jxi_LnyD7w,4298
|
|
26
25
|
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
26
|
anydi/ext/starlette/middleware.py,sha256=PKip_omFZDgg7h2OY-nnV2OIS1MbbmrrOJBwG7_Peuw,793
|
|
28
27
|
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
anydi-0.
|
|
30
|
-
anydi-0.
|
|
31
|
-
anydi-0.
|
|
32
|
-
anydi-0.
|
|
33
|
-
anydi-0.
|
|
28
|
+
anydi-0.33.1.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
|
|
29
|
+
anydi-0.33.1.dist-info/METADATA,sha256=u-YkBHTqAbah4ytfw7CXHEUNDVyjhBFUwD2dV8z84Jc,5112
|
|
30
|
+
anydi-0.33.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
31
|
+
anydi-0.33.1.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
|
|
32
|
+
anydi-0.33.1.dist-info/RECORD,,
|
anydi/_injector.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
from collections.abc import Awaitable
|
|
5
|
-
from functools import wraps
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast
|
|
7
|
-
|
|
8
|
-
from typing_extensions import ParamSpec
|
|
9
|
-
|
|
10
|
-
from ._logger import logger
|
|
11
|
-
from ._types import is_marker
|
|
12
|
-
from ._utils import get_full_qualname, get_typed_parameters
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from ._container import Container
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
T = TypeVar("T", bound=Any)
|
|
19
|
-
P = ParamSpec("P")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class Injector:
|
|
23
|
-
def __init__(self, container: Container) -> None:
|
|
24
|
-
self.container = container
|
|
25
|
-
|
|
26
|
-
def inject(
|
|
27
|
-
self,
|
|
28
|
-
call: Callable[P, T | Awaitable[T]],
|
|
29
|
-
) -> Callable[P, T | Awaitable[T]]:
|
|
30
|
-
# Check if the inner callable has already been wrapped
|
|
31
|
-
if hasattr(call, "__inject_wrapper__"):
|
|
32
|
-
return cast(Callable[P, Union[T, Awaitable[T]]], call.__inject_wrapper__)
|
|
33
|
-
|
|
34
|
-
injected_params = self._get_injected_params(call)
|
|
35
|
-
|
|
36
|
-
if inspect.iscoroutinefunction(call):
|
|
37
|
-
|
|
38
|
-
@wraps(call)
|
|
39
|
-
async def awrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
40
|
-
for name, annotation in injected_params.items():
|
|
41
|
-
kwargs[name] = await self.container.aresolve(annotation)
|
|
42
|
-
return cast(T, await call(*args, **kwargs))
|
|
43
|
-
|
|
44
|
-
call.__inject_wrapper__ = awrapper # type: ignore[attr-defined]
|
|
45
|
-
|
|
46
|
-
return awrapper
|
|
47
|
-
|
|
48
|
-
@wraps(call)
|
|
49
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
50
|
-
for name, annotation in injected_params.items():
|
|
51
|
-
kwargs[name] = self.container.resolve(annotation)
|
|
52
|
-
return cast(T, call(*args, **kwargs))
|
|
53
|
-
|
|
54
|
-
call.__inject_wrapper__ = wrapper # type: ignore[attr-defined]
|
|
55
|
-
|
|
56
|
-
return wrapper
|
|
57
|
-
|
|
58
|
-
def _get_injected_params(self, call: Callable[..., Any]) -> dict[str, Any]:
|
|
59
|
-
"""Get the injected parameters of a callable object."""
|
|
60
|
-
injected_params = {}
|
|
61
|
-
for parameter in get_typed_parameters(call):
|
|
62
|
-
if not is_marker(parameter.default):
|
|
63
|
-
continue
|
|
64
|
-
try:
|
|
65
|
-
self._validate_injected_parameter(call, parameter)
|
|
66
|
-
except LookupError as exc:
|
|
67
|
-
if not self.container.strict:
|
|
68
|
-
logger.debug(
|
|
69
|
-
f"Cannot validate the `{get_full_qualname(call)}` parameter "
|
|
70
|
-
f"`{parameter.name}` with an annotation of "
|
|
71
|
-
f"`{get_full_qualname(parameter.annotation)} due to being "
|
|
72
|
-
"in non-strict mode. It will be validated at the first call."
|
|
73
|
-
)
|
|
74
|
-
else:
|
|
75
|
-
raise exc
|
|
76
|
-
injected_params[parameter.name] = parameter.annotation
|
|
77
|
-
return injected_params
|
|
78
|
-
|
|
79
|
-
def _validate_injected_parameter(
|
|
80
|
-
self, call: Callable[..., Any], parameter: inspect.Parameter
|
|
81
|
-
) -> None:
|
|
82
|
-
"""Validate an injected parameter."""
|
|
83
|
-
if parameter.annotation is inspect.Parameter.empty:
|
|
84
|
-
raise TypeError(
|
|
85
|
-
f"Missing `{get_full_qualname(call)}` parameter "
|
|
86
|
-
f"`{parameter.name}` annotation."
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if not self.container.is_registered(parameter.annotation):
|
|
90
|
-
raise LookupError(
|
|
91
|
-
f"`{get_full_qualname(call)}` has an unknown dependency parameter "
|
|
92
|
-
f"`{parameter.name}` with an annotation of "
|
|
93
|
-
f"`{get_full_qualname(parameter.annotation)}`."
|
|
94
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|