anydi 0.34.1a1__py3-none-any.whl → 0.36.0__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 +171 -106
- anydi/_provider.py +12 -8
- anydi/_types.py +11 -8
- anydi/ext/pytest_plugin.py +33 -15
- {anydi-0.34.1a1.dist-info → anydi-0.36.0.dist-info}/METADATA +1 -1
- {anydi-0.34.1a1.dist-info → anydi-0.36.0.dist-info}/RECORD +9 -10
- anydi/_logger.py +0 -3
- {anydi-0.34.1a1.dist-info → anydi-0.36.0.dist-info}/LICENSE +0 -0
- {anydi-0.34.1a1.dist-info → anydi-0.36.0.dist-info}/WHEEL +0 -0
- {anydi-0.34.1a1.dist-info → anydi-0.36.0.dist-info}/entry_points.txt +0 -0
anydi/_container.py
CHANGED
|
@@ -6,6 +6,7 @@ import contextlib
|
|
|
6
6
|
import functools
|
|
7
7
|
import importlib
|
|
8
8
|
import inspect
|
|
9
|
+
import logging
|
|
9
10
|
import pkgutil
|
|
10
11
|
import threading
|
|
11
12
|
import types
|
|
@@ -19,13 +20,12 @@ from weakref import WeakKeyDictionary
|
|
|
19
20
|
from typing_extensions import Concatenate, ParamSpec, Self, final
|
|
20
21
|
|
|
21
22
|
from ._context import InstanceContext
|
|
22
|
-
from ._logger import logger
|
|
23
23
|
from ._provider import Provider
|
|
24
24
|
from ._types import (
|
|
25
25
|
AnyInterface,
|
|
26
26
|
Dependency,
|
|
27
|
-
DependencyWrapper,
|
|
28
27
|
InjectableDecoratorArgs,
|
|
28
|
+
InstanceProxy,
|
|
29
29
|
ProviderDecoratorArgs,
|
|
30
30
|
Scope,
|
|
31
31
|
is_event_type,
|
|
@@ -74,6 +74,7 @@ class Module(metaclass=ModuleMeta):
|
|
|
74
74
|
"""Configure the AnyDI container with providers and their dependencies."""
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
# noinspection PyShadowingNames
|
|
77
78
|
@final
|
|
78
79
|
class Container:
|
|
79
80
|
"""AnyDI is a dependency injection container."""
|
|
@@ -86,8 +87,12 @@ class Container:
|
|
|
86
87
|
| None = None,
|
|
87
88
|
strict: bool = False,
|
|
88
89
|
testing: bool = False,
|
|
90
|
+
logger: logging.Logger | None = None,
|
|
89
91
|
) -> None:
|
|
90
92
|
self._providers: dict[type[Any], Provider] = {}
|
|
93
|
+
self._strict = strict
|
|
94
|
+
self._testing = testing
|
|
95
|
+
self._logger = logger or logging.getLogger(__name__)
|
|
91
96
|
self._resources: dict[str, list[type[Any]]] = defaultdict(list)
|
|
92
97
|
self._singleton_context = InstanceContext()
|
|
93
98
|
self._singleton_lock = threading.RLock()
|
|
@@ -96,8 +101,6 @@ class Container:
|
|
|
96
101
|
"request_context", default=None
|
|
97
102
|
)
|
|
98
103
|
self._override_instances: dict[type[Any], Any] = {}
|
|
99
|
-
self._strict = strict
|
|
100
|
-
self._testing = testing
|
|
101
104
|
self._unresolved_interfaces: set[type[Any]] = set()
|
|
102
105
|
self._inject_cache: WeakKeyDictionary[
|
|
103
106
|
Callable[..., Any], Callable[..., Any]
|
|
@@ -106,7 +109,7 @@ class Container:
|
|
|
106
109
|
# Register providers
|
|
107
110
|
providers = providers or []
|
|
108
111
|
for provider in providers:
|
|
109
|
-
self._register_provider(provider)
|
|
112
|
+
self._register_provider(provider, False)
|
|
110
113
|
|
|
111
114
|
# Register modules
|
|
112
115
|
modules = modules or []
|
|
@@ -128,6 +131,11 @@ class Container:
|
|
|
128
131
|
"""Get the registered providers."""
|
|
129
132
|
return self._providers
|
|
130
133
|
|
|
134
|
+
@property
|
|
135
|
+
def logger(self) -> logging.Logger:
|
|
136
|
+
"""Get the logger instance."""
|
|
137
|
+
return self._logger
|
|
138
|
+
|
|
131
139
|
def is_registered(self, interface: AnyInterface) -> bool:
|
|
132
140
|
"""Check if a provider is registered for the specified interface."""
|
|
133
141
|
return interface in self._providers
|
|
@@ -142,10 +150,10 @@ class Container:
|
|
|
142
150
|
) -> Provider:
|
|
143
151
|
"""Register a provider for the specified interface."""
|
|
144
152
|
provider = Provider(call=call, scope=scope, interface=interface)
|
|
145
|
-
return self._register_provider(provider, override
|
|
153
|
+
return self._register_provider(provider, override)
|
|
146
154
|
|
|
147
155
|
def _register_provider(
|
|
148
|
-
self, provider: Provider,
|
|
156
|
+
self, provider: Provider, override: bool, /, **defaults: Any
|
|
149
157
|
) -> Provider:
|
|
150
158
|
"""Register a provider."""
|
|
151
159
|
if provider.interface in self._providers:
|
|
@@ -158,7 +166,7 @@ class Container:
|
|
|
158
166
|
"already registered."
|
|
159
167
|
)
|
|
160
168
|
|
|
161
|
-
self._validate_sub_providers(provider)
|
|
169
|
+
self._validate_sub_providers(provider, **defaults)
|
|
162
170
|
self._set_provider(provider)
|
|
163
171
|
return provider
|
|
164
172
|
|
|
@@ -196,7 +204,7 @@ class Container:
|
|
|
196
204
|
) from exc
|
|
197
205
|
|
|
198
206
|
def _get_or_register_provider(
|
|
199
|
-
self, interface: AnyInterface, parent_scope: Scope | None
|
|
207
|
+
self, interface: AnyInterface, parent_scope: Scope | None, /, **defaults: Any
|
|
200
208
|
) -> Provider:
|
|
201
209
|
"""Get or register a provider by interface."""
|
|
202
210
|
try:
|
|
@@ -212,8 +220,10 @@ class Container:
|
|
|
212
220
|
scope = getattr(interface, "__scope__", parent_scope)
|
|
213
221
|
# Try to detect scope
|
|
214
222
|
if scope is None:
|
|
215
|
-
scope = self._detect_scope(interface)
|
|
216
|
-
|
|
223
|
+
scope = self._detect_scope(interface, **defaults)
|
|
224
|
+
scope = scope or "transient"
|
|
225
|
+
provider = Provider(call=interface, scope=scope, interface=interface)
|
|
226
|
+
return self._register_provider(provider, False, **defaults)
|
|
217
227
|
raise
|
|
218
228
|
|
|
219
229
|
def _set_provider(self, provider: Provider) -> None:
|
|
@@ -229,13 +239,11 @@ class Container:
|
|
|
229
239
|
if provider.is_resource:
|
|
230
240
|
self._resources[provider.scope].remove(provider.interface)
|
|
231
241
|
|
|
232
|
-
def _validate_sub_providers(self, provider: Provider) -> None:
|
|
242
|
+
def _validate_sub_providers(self, provider: Provider, /, **defaults: Any) -> None:
|
|
233
243
|
"""Validate the sub-providers of a provider."""
|
|
234
244
|
|
|
235
245
|
for parameter in provider.parameters:
|
|
236
|
-
annotation
|
|
237
|
-
|
|
238
|
-
if annotation is inspect.Parameter.empty:
|
|
246
|
+
if parameter.annotation is inspect.Parameter.empty:
|
|
239
247
|
raise TypeError(
|
|
240
248
|
f"Missing provider `{provider}` "
|
|
241
249
|
f"dependency `{parameter.name}` annotation."
|
|
@@ -243,11 +251,10 @@ class Container:
|
|
|
243
251
|
|
|
244
252
|
try:
|
|
245
253
|
sub_provider = self._get_or_register_provider(
|
|
246
|
-
annotation,
|
|
254
|
+
parameter.annotation, provider.scope
|
|
247
255
|
)
|
|
248
256
|
except LookupError:
|
|
249
|
-
|
|
250
|
-
if not self.strict and parameter.default is not inspect.Parameter.empty:
|
|
257
|
+
if self._parameter_has_default(parameter, **defaults):
|
|
251
258
|
continue
|
|
252
259
|
|
|
253
260
|
if provider.scope not in {"singleton", "transient"}:
|
|
@@ -255,7 +262,7 @@ class Container:
|
|
|
255
262
|
continue
|
|
256
263
|
raise ValueError(
|
|
257
264
|
f"The provider `{provider}` depends on `{parameter.name}` of type "
|
|
258
|
-
f"`{get_full_qualname(annotation)}`, which "
|
|
265
|
+
f"`{get_full_qualname(parameter.annotation)}`, which "
|
|
259
266
|
"has not been registered or set. To resolve this, ensure that "
|
|
260
267
|
f"`{parameter.name}` is registered before attempting to use it."
|
|
261
268
|
) from None
|
|
@@ -268,15 +275,17 @@ class Container:
|
|
|
268
275
|
"Please ensure all providers are registered with matching scopes."
|
|
269
276
|
)
|
|
270
277
|
|
|
271
|
-
def _detect_scope(self, call: Callable[..., Any]) -> Scope | None:
|
|
278
|
+
def _detect_scope(self, call: Callable[..., Any], **defaults: Any) -> Scope | None:
|
|
272
279
|
"""Detect the scope for a callable."""
|
|
273
280
|
scopes = set()
|
|
274
281
|
|
|
275
282
|
for parameter in get_typed_parameters(call):
|
|
276
283
|
try:
|
|
277
|
-
sub_provider = self._get_or_register_provider(
|
|
284
|
+
sub_provider = self._get_or_register_provider(
|
|
285
|
+
parameter.annotation, None
|
|
286
|
+
)
|
|
278
287
|
except LookupError:
|
|
279
|
-
if
|
|
288
|
+
if self._parameter_has_default(parameter, **defaults):
|
|
280
289
|
continue
|
|
281
290
|
raise
|
|
282
291
|
scope = sub_provider.scope
|
|
@@ -297,6 +306,13 @@ class Container:
|
|
|
297
306
|
|
|
298
307
|
return None
|
|
299
308
|
|
|
309
|
+
def _parameter_has_default(
|
|
310
|
+
self, parameter: inspect.Parameter, /, **defaults: Any
|
|
311
|
+
) -> bool:
|
|
312
|
+
return (defaults and parameter.name in defaults) or (
|
|
313
|
+
not self.strict and parameter.default is not inspect.Parameter.empty
|
|
314
|
+
)
|
|
315
|
+
|
|
300
316
|
def register_module(
|
|
301
317
|
self, module: Module | type[Module] | Callable[[Container], None] | str
|
|
302
318
|
) -> None:
|
|
@@ -436,9 +452,9 @@ class Container:
|
|
|
436
452
|
if interface in self._override_instances:
|
|
437
453
|
return cast(T, self._override_instances[interface])
|
|
438
454
|
|
|
439
|
-
provider = self._get_or_register_provider(interface)
|
|
455
|
+
provider = self._get_or_register_provider(interface, None)
|
|
440
456
|
if provider.scope == "transient":
|
|
441
|
-
instance, created = self._create_instance(provider), True
|
|
457
|
+
instance, created = self._create_instance(provider, None), True
|
|
442
458
|
else:
|
|
443
459
|
context = self._get_scoped_context(provider.scope)
|
|
444
460
|
if provider.scope == "singleton":
|
|
@@ -465,9 +481,9 @@ class Container:
|
|
|
465
481
|
if interface in self._override_instances:
|
|
466
482
|
return cast(T, self._override_instances[interface])
|
|
467
483
|
|
|
468
|
-
provider = self._get_or_register_provider(interface)
|
|
484
|
+
provider = self._get_or_register_provider(interface, None)
|
|
469
485
|
if provider.scope == "transient":
|
|
470
|
-
instance, created = await self._acreate_instance(provider), True
|
|
486
|
+
instance, created = await self._acreate_instance(provider, None), True
|
|
471
487
|
else:
|
|
472
488
|
context = self._get_scoped_context(provider.scope)
|
|
473
489
|
if provider.scope == "singleton":
|
|
@@ -483,15 +499,46 @@ class Container:
|
|
|
483
499
|
self._patch_test_resolver(instance)
|
|
484
500
|
return cast(T, instance)
|
|
485
501
|
|
|
502
|
+
def create(self, interface: type[T], **defaults: Any) -> T:
|
|
503
|
+
"""Create an instance by interface."""
|
|
504
|
+
provider = self._get_or_register_provider(interface, None, **defaults)
|
|
505
|
+
if provider.scope == "transient":
|
|
506
|
+
instance = self._create_instance(provider, None, **defaults)
|
|
507
|
+
else:
|
|
508
|
+
context = self._get_scoped_context(provider.scope)
|
|
509
|
+
if provider.scope == "singleton":
|
|
510
|
+
with self._singleton_lock:
|
|
511
|
+
instance = self._create_instance(provider, context, **defaults)
|
|
512
|
+
else:
|
|
513
|
+
instance = self._create_instance(provider, context, **defaults)
|
|
514
|
+
return cast(T, instance)
|
|
515
|
+
|
|
516
|
+
async def acreate(self, interface: type[T], **defaults: Any) -> T:
|
|
517
|
+
"""Create an instance by interface."""
|
|
518
|
+
provider = self._get_or_register_provider(interface, None, **defaults)
|
|
519
|
+
if provider.scope == "transient":
|
|
520
|
+
instance = await self._acreate_instance(provider, None, **defaults)
|
|
521
|
+
else:
|
|
522
|
+
context = self._get_scoped_context(provider.scope)
|
|
523
|
+
if provider.scope == "singleton":
|
|
524
|
+
async with self._singleton_async_lock:
|
|
525
|
+
instance = await self._acreate_instance(
|
|
526
|
+
provider, context, **defaults
|
|
527
|
+
)
|
|
528
|
+
else:
|
|
529
|
+
instance = await self._acreate_instance(provider, context, **defaults)
|
|
530
|
+
return cast(T, instance)
|
|
531
|
+
|
|
486
532
|
def _get_or_create_instance(
|
|
487
533
|
self, provider: Provider, context: InstanceContext
|
|
488
534
|
) -> tuple[Any, bool]:
|
|
489
535
|
"""Get an instance of a dependency from the scoped context."""
|
|
490
536
|
instance = context.get(provider.interface)
|
|
491
537
|
if instance is None:
|
|
492
|
-
instance = self._create_instance(provider, context
|
|
493
|
-
|
|
494
|
-
|
|
538
|
+
instance = self._create_instance(provider, context)
|
|
539
|
+
if not self._override_instances:
|
|
540
|
+
context.set(provider.interface, instance)
|
|
541
|
+
return instance, True
|
|
495
542
|
return instance, False
|
|
496
543
|
|
|
497
544
|
async def _aget_or_create_instance(
|
|
@@ -500,15 +547,14 @@ class Container:
|
|
|
500
547
|
"""Get an async instance of a dependency from the scoped context."""
|
|
501
548
|
instance = context.get(provider.interface)
|
|
502
549
|
if instance is None:
|
|
503
|
-
instance = await self._acreate_instance(provider, context
|
|
504
|
-
|
|
505
|
-
|
|
550
|
+
instance = await self._acreate_instance(provider, context)
|
|
551
|
+
if not self._override_instances:
|
|
552
|
+
context.set(provider.interface, instance)
|
|
553
|
+
return instance, True
|
|
506
554
|
return instance, False
|
|
507
555
|
|
|
508
556
|
def _create_instance(
|
|
509
|
-
self,
|
|
510
|
-
provider: Provider,
|
|
511
|
-
context: InstanceContext | None = None,
|
|
557
|
+
self, provider: Provider, context: InstanceContext | None, /, **defaults: Any
|
|
512
558
|
) -> Any:
|
|
513
559
|
"""Create an instance using the provider."""
|
|
514
560
|
if provider.is_async:
|
|
@@ -517,29 +563,29 @@ class Container:
|
|
|
517
563
|
"synchronous mode."
|
|
518
564
|
)
|
|
519
565
|
|
|
520
|
-
|
|
566
|
+
provider_kwargs = self._get_provided_kwargs(provider, context, **defaults)
|
|
521
567
|
|
|
522
568
|
if provider.is_generator:
|
|
523
569
|
if context is None:
|
|
524
570
|
raise ValueError("The context is required for generator providers.")
|
|
525
|
-
cm = contextlib.contextmanager(provider.call)(
|
|
571
|
+
cm = contextlib.contextmanager(provider.call)(**provider_kwargs)
|
|
526
572
|
return context.enter(cm)
|
|
527
573
|
|
|
528
|
-
instance = provider.call(
|
|
574
|
+
instance = provider.call(**provider_kwargs)
|
|
529
575
|
if context is not None and is_context_manager(instance):
|
|
530
576
|
context.enter(instance)
|
|
531
577
|
return instance
|
|
532
578
|
|
|
533
579
|
async def _acreate_instance(
|
|
534
|
-
self,
|
|
535
|
-
provider: Provider,
|
|
536
|
-
context: InstanceContext | None = None,
|
|
580
|
+
self, provider: Provider, context: InstanceContext | None, /, **defaults: Any
|
|
537
581
|
) -> Any:
|
|
538
582
|
"""Create an instance asynchronously using the provider."""
|
|
539
|
-
|
|
583
|
+
provider_kwargs = await self._aget_provided_kwargs(
|
|
584
|
+
provider, context, **defaults
|
|
585
|
+
)
|
|
540
586
|
|
|
541
587
|
if provider.is_coroutine:
|
|
542
|
-
instance = await provider.call(
|
|
588
|
+
instance = await provider.call(**provider_kwargs)
|
|
543
589
|
if context is not None and is_async_context_manager(instance):
|
|
544
590
|
await context.aenter(instance)
|
|
545
591
|
return instance
|
|
@@ -549,7 +595,7 @@ class Container:
|
|
|
549
595
|
raise ValueError(
|
|
550
596
|
"The async stack is required for async generator providers."
|
|
551
597
|
)
|
|
552
|
-
cm = contextlib.asynccontextmanager(provider.call)(
|
|
598
|
+
cm = contextlib.asynccontextmanager(provider.call)(**provider_kwargs)
|
|
553
599
|
return await context.aenter(cm)
|
|
554
600
|
|
|
555
601
|
if provider.is_generator:
|
|
@@ -557,83 +603,102 @@ class Container:
|
|
|
557
603
|
def _create() -> Any:
|
|
558
604
|
if context is None:
|
|
559
605
|
raise ValueError("The stack is required for generator providers.")
|
|
560
|
-
cm = contextlib.contextmanager(provider.call)(
|
|
606
|
+
cm = contextlib.contextmanager(provider.call)(**provider_kwargs)
|
|
561
607
|
return context.enter(cm)
|
|
562
608
|
|
|
563
609
|
return await run_async(_create)
|
|
564
610
|
|
|
565
|
-
instance = await run_async(provider.call,
|
|
611
|
+
instance = await run_async(provider.call, **provider_kwargs)
|
|
566
612
|
if context is not None and is_async_context_manager(instance):
|
|
567
613
|
await context.aenter(instance)
|
|
568
614
|
return instance
|
|
569
615
|
|
|
570
|
-
def
|
|
616
|
+
def _get_provided_kwargs(
|
|
617
|
+
self, provider: Provider, context: InstanceContext | None, /, **defaults: Any
|
|
618
|
+
) -> dict[str, Any]:
|
|
619
|
+
"""Retrieve the arguments for a provider."""
|
|
620
|
+
provided_kwargs = {}
|
|
621
|
+
for parameter in provider.parameters:
|
|
622
|
+
instance = self._get_provider_instance(
|
|
623
|
+
provider, parameter, context, **defaults
|
|
624
|
+
)
|
|
625
|
+
provided_kwargs[parameter.name] = instance
|
|
626
|
+
return {**defaults, **provided_kwargs}
|
|
627
|
+
|
|
628
|
+
def _get_provider_instance(
|
|
571
629
|
self,
|
|
572
630
|
provider: Provider,
|
|
631
|
+
parameter: inspect.Parameter,
|
|
573
632
|
context: InstanceContext | None,
|
|
574
|
-
|
|
575
|
-
**
|
|
576
|
-
) ->
|
|
577
|
-
"""Retrieve
|
|
578
|
-
|
|
579
|
-
|
|
633
|
+
/,
|
|
634
|
+
**defaults: Any,
|
|
635
|
+
) -> Any:
|
|
636
|
+
"""Retrieve an instance of a dependency from the scoped context."""
|
|
637
|
+
if parameter.name in defaults:
|
|
638
|
+
return defaults[parameter.name]
|
|
580
639
|
|
|
640
|
+
# Get instance from overrides or context cache
|
|
641
|
+
if parameter.annotation in self._override_instances:
|
|
642
|
+
return self._override_instances[parameter.annotation]
|
|
643
|
+
elif context and parameter.annotation in context:
|
|
644
|
+
return context[parameter.annotation]
|
|
645
|
+
|
|
646
|
+
# Resolve the instance
|
|
647
|
+
try:
|
|
648
|
+
instance = self._resolve_parameter(provider, parameter)
|
|
649
|
+
except LookupError:
|
|
650
|
+
if parameter.default is inspect.Parameter.empty:
|
|
651
|
+
raise
|
|
652
|
+
instance = parameter.default
|
|
653
|
+
|
|
654
|
+
# Wrap the instance in a proxy for testing
|
|
655
|
+
if self.testing:
|
|
656
|
+
return InstanceProxy(interface=parameter.annotation, instance=instance)
|
|
657
|
+
|
|
658
|
+
return instance
|
|
659
|
+
|
|
660
|
+
async def _aget_provided_kwargs(
|
|
661
|
+
self, provider: Provider, context: InstanceContext | None, /, **defaults: Any
|
|
662
|
+
) -> dict[str, Any]:
|
|
663
|
+
"""Asynchronously retrieve the arguments for a provider."""
|
|
664
|
+
provided_kwargs = {}
|
|
581
665
|
for parameter in provider.parameters:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
try:
|
|
588
|
-
instance = self._resolve_parameter(provider, parameter)
|
|
589
|
-
except LookupError:
|
|
590
|
-
if parameter.default is inspect.Parameter.empty:
|
|
591
|
-
raise
|
|
592
|
-
instance = parameter.default
|
|
593
|
-
else:
|
|
594
|
-
if self.testing:
|
|
595
|
-
instance = DependencyWrapper(
|
|
596
|
-
interface=parameter.annotation, instance=instance
|
|
597
|
-
)
|
|
598
|
-
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
599
|
-
provided_args.append(instance)
|
|
600
|
-
else:
|
|
601
|
-
provided_kwargs[parameter.name] = instance
|
|
602
|
-
return provided_args, provided_kwargs
|
|
666
|
+
instance = await self._aget_provider_instance(
|
|
667
|
+
provider, parameter, context, **defaults
|
|
668
|
+
)
|
|
669
|
+
provided_kwargs[parameter.name] = instance
|
|
670
|
+
return {**defaults, **provided_kwargs}
|
|
603
671
|
|
|
604
|
-
async def
|
|
672
|
+
async def _aget_provider_instance(
|
|
605
673
|
self,
|
|
606
674
|
provider: Provider,
|
|
675
|
+
parameter: inspect.Parameter,
|
|
607
676
|
context: InstanceContext | None,
|
|
608
|
-
|
|
609
|
-
**
|
|
610
|
-
) ->
|
|
611
|
-
"""Asynchronously retrieve
|
|
612
|
-
|
|
613
|
-
|
|
677
|
+
/,
|
|
678
|
+
**defaults: Any,
|
|
679
|
+
) -> Any:
|
|
680
|
+
"""Asynchronously retrieve an instance of a dependency from the context."""
|
|
681
|
+
if parameter.name in defaults:
|
|
682
|
+
return defaults[parameter.name]
|
|
614
683
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
provided_args.append(instance)
|
|
634
|
-
else:
|
|
635
|
-
provided_kwargs[parameter.name] = instance
|
|
636
|
-
return provided_args, provided_kwargs
|
|
684
|
+
# Get instance from overrides or context cache
|
|
685
|
+
if parameter.annotation in self._override_instances:
|
|
686
|
+
return self._override_instances[parameter.annotation]
|
|
687
|
+
elif context and parameter.annotation in context:
|
|
688
|
+
return context[parameter.annotation]
|
|
689
|
+
|
|
690
|
+
# Resolve the instance
|
|
691
|
+
try:
|
|
692
|
+
instance = await self._aresolve_parameter(provider, parameter)
|
|
693
|
+
except LookupError:
|
|
694
|
+
if parameter.default is inspect.Parameter.empty:
|
|
695
|
+
raise
|
|
696
|
+
instance = parameter.default
|
|
697
|
+
|
|
698
|
+
# Wrap the instance in a proxy for testing
|
|
699
|
+
if self.testing:
|
|
700
|
+
return InstanceProxy(interface=parameter.annotation, instance=instance)
|
|
701
|
+
return instance
|
|
637
702
|
|
|
638
703
|
def _resolve_parameter(
|
|
639
704
|
self, provider: Provider, parameter: inspect.Parameter
|
|
@@ -667,7 +732,7 @@ class Container:
|
|
|
667
732
|
wrapped = {
|
|
668
733
|
name: value.interface
|
|
669
734
|
for name, value in instance.__dict__.items()
|
|
670
|
-
if isinstance(value,
|
|
735
|
+
if isinstance(value, InstanceProxy)
|
|
671
736
|
}
|
|
672
737
|
|
|
673
738
|
# Custom resolver function
|
|
@@ -728,7 +793,7 @@ class Container:
|
|
|
728
793
|
|
|
729
794
|
def decorator(call: Callable[P, T]) -> Callable[P, T]:
|
|
730
795
|
provider = Provider(call=call, scope=scope)
|
|
731
|
-
self._register_provider(provider, override
|
|
796
|
+
self._register_provider(provider, override)
|
|
732
797
|
return call
|
|
733
798
|
|
|
734
799
|
return decorator
|
|
@@ -790,7 +855,7 @@ class Container:
|
|
|
790
855
|
self._validate_injected_parameter(call, parameter)
|
|
791
856
|
except LookupError as exc:
|
|
792
857
|
if not self.strict:
|
|
793
|
-
logger.debug(
|
|
858
|
+
self.logger.debug(
|
|
794
859
|
f"Cannot validate the `{get_full_qualname(call)}` parameter "
|
|
795
860
|
f"`{parameter.name}` with an annotation of "
|
|
796
861
|
f"`{get_full_qualname(parameter.annotation)} due to being "
|
anydi/_provider.py
CHANGED
|
@@ -209,13 +209,17 @@ class Provider:
|
|
|
209
209
|
|
|
210
210
|
def _detect_parameters(self, signature: inspect.Signature) -> None:
|
|
211
211
|
"""Detect the parameters of the callable provider."""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
parameters = []
|
|
213
|
+
for parameter in signature.parameters.values():
|
|
214
|
+
if parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
|
|
215
|
+
raise TypeError(
|
|
216
|
+
f"Positional-only parameter `{parameter.name}` is not allowed "
|
|
217
|
+
f"in the provider `{self}`."
|
|
218
218
|
)
|
|
219
|
+
annotation = get_typed_annotation(
|
|
220
|
+
parameter.annotation,
|
|
221
|
+
self._call_globals,
|
|
222
|
+
module=self._call_module,
|
|
219
223
|
)
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
parameters.append(parameter.replace(annotation=annotation))
|
|
225
|
+
self._parameters = parameters
|
anydi/_types.py
CHANGED
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
from collections.abc import Iterable
|
|
5
|
-
from dataclasses import dataclass
|
|
6
5
|
from types import ModuleType
|
|
7
6
|
from typing import Annotated, Any, NamedTuple, Union
|
|
8
7
|
|
|
@@ -38,24 +37,28 @@ def is_event_type(obj: Any) -> bool:
|
|
|
38
37
|
return inspect.isclass(obj) and issubclass(obj, Event)
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
instance: Any
|
|
40
|
+
class InstanceProxy:
|
|
41
|
+
__slots__ = ("interface", "instance")
|
|
42
|
+
|
|
43
|
+
def __init__(self, *, interface: type[Any], instance: Any):
|
|
44
|
+
self.interface = interface
|
|
45
|
+
self.instance = instance
|
|
45
46
|
|
|
46
47
|
def __getattribute__(self, name: str) -> Any:
|
|
47
|
-
if name in
|
|
48
|
+
if name in ("interface", "instance"):
|
|
48
49
|
return object.__getattribute__(self, name)
|
|
49
50
|
return getattr(self.instance, name)
|
|
50
51
|
|
|
52
|
+
def __repr__(self) -> str:
|
|
53
|
+
return f"InstanceProxy({self.interface!r})"
|
|
54
|
+
|
|
51
55
|
|
|
52
56
|
class ProviderDecoratorArgs(NamedTuple):
|
|
53
57
|
scope: Scope
|
|
54
58
|
override: bool
|
|
55
59
|
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
class Dependency:
|
|
61
|
+
class Dependency(NamedTuple):
|
|
59
62
|
member: Any
|
|
60
63
|
module: ModuleType
|
|
61
64
|
|
anydi/ext/pytest_plugin.py
CHANGED
|
@@ -6,6 +6,8 @@ from collections.abc import Iterator
|
|
|
6
6
|
from typing import Any, Callable, cast
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
+
from _pytest.python import async_warn_and_skip
|
|
10
|
+
from anyio.pytest_plugin import extract_backend_and_options, get_runner
|
|
9
11
|
|
|
10
12
|
from anydi import Container
|
|
11
13
|
from anydi._utils import get_typed_parameters
|
|
@@ -113,28 +115,44 @@ def _anydi_inject(
|
|
|
113
115
|
|
|
114
116
|
|
|
115
117
|
@pytest.fixture(autouse=True)
|
|
116
|
-
|
|
118
|
+
def _anydi_ainject(
|
|
117
119
|
request: pytest.FixtureRequest,
|
|
118
120
|
_anydi_should_inject: bool,
|
|
119
121
|
_anydi_injected_parameter_iterator: Callable[[], Iterator[tuple[str, Any]]],
|
|
120
122
|
_anydi_unresolved: list[str],
|
|
121
123
|
) -> None:
|
|
122
124
|
"""Inject dependencies into the test function."""
|
|
123
|
-
if
|
|
125
|
+
if (
|
|
126
|
+
not inspect.iscoroutinefunction(request.function)
|
|
127
|
+
and not inspect.isasyncgenfunction(request.function)
|
|
128
|
+
or not _anydi_should_inject
|
|
129
|
+
):
|
|
124
130
|
return
|
|
125
131
|
|
|
126
|
-
#
|
|
127
|
-
|
|
132
|
+
# Skip if the anyio backend is not available
|
|
133
|
+
if "anyio_backend" not in request.fixturenames:
|
|
134
|
+
async_warn_and_skip(request.node.nodeid)
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
continue
|
|
136
|
+
async def _awrapper() -> None:
|
|
137
|
+
# Setup the container
|
|
138
|
+
container = cast(Container, request.getfixturevalue("anydi_setup_container"))
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
for argname, interface in _anydi_injected_parameter_iterator():
|
|
141
|
+
# Skip if the interface is not registered
|
|
142
|
+
if container.strict and not container.is_registered(interface):
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
request.node.funcargs[argname] = await container.aresolve(interface)
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"Failed to resolve dependency for argument '{argname}'.",
|
|
150
|
+
exc_info=exc,
|
|
151
|
+
)
|
|
152
|
+
_anydi_unresolved.append(interface)
|
|
153
|
+
|
|
154
|
+
anyio_backend = request.getfixturevalue("anyio_backend")
|
|
155
|
+
backend_name, backend_options = extract_backend_and_options(anyio_backend)
|
|
156
|
+
|
|
157
|
+
with get_runner(backend_name, backend_options) as runner:
|
|
158
|
+
runner.run_fixture(_awrapper, {})
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
anydi/__init__.py,sha256=OfRg2EfXD65pHTGQKhfkABMwUhw5LvsuTQV_Tv4V4wk,501
|
|
2
|
-
anydi/_container.py,sha256=
|
|
2
|
+
anydi/_container.py,sha256=2MvGilZn2qV1kyNiOkT9wiIbVAbaN5m1XvEyPUDj2ks,38028
|
|
3
3
|
anydi/_context.py,sha256=7LV_SL4QWkJeiG7_4D9PZ5lmU-MPzhofxC95zCgY9Gc,2651
|
|
4
|
-
anydi/
|
|
5
|
-
anydi/
|
|
6
|
-
anydi/_types.py,sha256=55Wvaxcs2DPpVXrMqhHebT_ZeGDnH-H_zhND306vaoU,1397
|
|
4
|
+
anydi/_provider.py,sha256=W42y8wbsnWbb9B9gI-pnEa-lsz68nK0VIm55CJW3pWg,7457
|
|
5
|
+
anydi/_types.py,sha256=Vttj9GTp9g0KKpK-uqolLfVZJPIM7f7_YL8bPlablcQ,1539
|
|
7
6
|
anydi/_utils.py,sha256=INI0jNIXrJ6LS4zqJymMO2yUEobpxmBGASf4G_vR6AU,4378
|
|
8
7
|
anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
8
|
anydi/ext/_utils.py,sha256=U6sRqWzccWUu7eMhbXX1NrwcaxitQF9cO1KxnKF37gw,2566
|
|
@@ -19,12 +18,12 @@ anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfy
|
|
|
19
18
|
anydi/ext/fastapi.py,sha256=AEL3ubu-LxUPHMMt1YIn3En_JZC7nyBKmKxmhka3O3c,2436
|
|
20
19
|
anydi/ext/faststream.py,sha256=qXnNGvAqWWp9kbhbQUE6EF_OPUiYQmtOH211_O7BI_0,1898
|
|
21
20
|
anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
|
|
22
|
-
anydi/ext/pytest_plugin.py,sha256=
|
|
21
|
+
anydi/ext/pytest_plugin.py,sha256=ShGhiZnP1KyMHhnc9Ci1RKAuHVhw628OTS2P2BLEOfc,5001
|
|
23
22
|
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
23
|
anydi/ext/starlette/middleware.py,sha256=9CQtGg5ZzUz2gFSzJr8U4BWzwNjK8XMctm3n52M77Z0,792
|
|
25
24
|
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
anydi-0.
|
|
27
|
-
anydi-0.
|
|
28
|
-
anydi-0.
|
|
29
|
-
anydi-0.
|
|
30
|
-
anydi-0.
|
|
25
|
+
anydi-0.36.0.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
|
|
26
|
+
anydi-0.36.0.dist-info/METADATA,sha256=GQLgpqFcAds69iABb3uo_n5ciJxXsLDfiN_b9PNtrZQ,5064
|
|
27
|
+
anydi-0.36.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
28
|
+
anydi-0.36.0.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
|
|
29
|
+
anydi-0.36.0.dist-info/RECORD,,
|
anydi/_logger.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|