anydi 0.34.1a1__tar.gz → 0.36.0__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.
Files changed (29) hide show
  1. {anydi-0.34.1a1 → anydi-0.36.0}/PKG-INFO +1 -1
  2. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/_container.py +171 -106
  3. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/_provider.py +12 -8
  4. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/_types.py +11 -8
  5. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/pytest_plugin.py +33 -15
  6. {anydi-0.34.1a1 → anydi-0.36.0}/pyproject.toml +1 -1
  7. anydi-0.34.1a1/anydi/_logger.py +0 -3
  8. {anydi-0.34.1a1 → anydi-0.36.0}/LICENSE +0 -0
  9. {anydi-0.34.1a1 → anydi-0.36.0}/README.md +0 -0
  10. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/__init__.py +0 -0
  11. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/_context.py +0 -0
  12. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/_utils.py +0 -0
  13. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/__init__.py +0 -0
  14. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/_utils.py +0 -0
  15. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/__init__.py +0 -0
  16. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/_container.py +0 -0
  17. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/_settings.py +0 -0
  18. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/_utils.py +0 -0
  19. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/apps.py +0 -0
  20. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/middleware.py +0 -0
  21. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/ninja/__init__.py +0 -0
  22. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/ninja/_operation.py +0 -0
  23. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/django/ninja/_signature.py +0 -0
  24. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/fastapi.py +0 -0
  25. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/faststream.py +0 -0
  26. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/pydantic_settings.py +0 -0
  27. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/starlette/__init__.py +0 -0
  28. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/ext/starlette/middleware.py +0 -0
  29. {anydi-0.34.1a1 → anydi-0.36.0}/anydi/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anydi
3
- Version: 0.34.1a1
3
+ Version: 0.36.0
4
4
  Summary: Dependency Injection library
5
5
  Home-page: https://github.com/antonrh/anydi
6
6
  License: MIT
@@ -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=override)
153
+ return self._register_provider(provider, override)
146
154
 
147
155
  def _register_provider(
148
- self, provider: Provider, *, override: bool = False
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 = 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
- return self.register(interface, interface, scope=scope or "transient")
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 = parameter.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, parent_scope=provider.scope
254
+ parameter.annotation, provider.scope
247
255
  )
248
256
  except LookupError:
249
- # Skip unresolved interfaces in non-strict mode
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(parameter.annotation)
284
+ sub_provider = self._get_or_register_provider(
285
+ parameter.annotation, None
286
+ )
278
287
  except LookupError:
279
- if not self.strict and parameter.default is not inspect.Parameter.empty:
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=context)
493
- context.set(provider.interface, instance)
494
- return instance, True
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=context)
504
- context.set(provider.interface, instance)
505
- return instance, True
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
- args, kwargs = self._get_provided_args(provider, context=context)
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)(*args, **kwargs)
571
+ cm = contextlib.contextmanager(provider.call)(**provider_kwargs)
526
572
  return context.enter(cm)
527
573
 
528
- instance = provider.call(*args, **kwargs)
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
- args, kwargs = await self._aget_provided_args(provider, context=context)
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(*args, **kwargs)
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)(*args, **kwargs)
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)(*args, **kwargs)
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, *args, **kwargs)
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 _get_provided_args(
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
- *args: Any,
575
- **kwargs: Any,
576
- ) -> tuple[list[Any], dict[str, Any]]:
577
- """Retrieve the arguments for a provider."""
578
- provided_args: list[Any] = []
579
- provided_kwargs: dict[str, Any] = {}
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
- if parameter.annotation in self._override_instances:
583
- instance = self._override_instances[parameter.annotation]
584
- elif context and parameter.annotation in context:
585
- instance = context[parameter.annotation]
586
- else:
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 _aget_provided_args(
672
+ async def _aget_provider_instance(
605
673
  self,
606
674
  provider: Provider,
675
+ parameter: inspect.Parameter,
607
676
  context: InstanceContext | None,
608
- *args: Any,
609
- **kwargs: Any,
610
- ) -> tuple[list[Any], dict[str, Any]]:
611
- """Asynchronously retrieve the arguments for a provider."""
612
- provided_args: list[Any] = []
613
- provided_kwargs: dict[str, Any] = {}
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
- for parameter in provider.parameters:
616
- if parameter.annotation in self._override_instances:
617
- instance = self._override_instances[parameter.annotation]
618
- elif context and parameter.annotation in context:
619
- instance = context[parameter.annotation]
620
- else:
621
- try:
622
- instance = await self._aresolve_parameter(provider, parameter)
623
- except LookupError:
624
- if parameter.default is inspect.Parameter.empty:
625
- raise
626
- instance = parameter.default
627
- else:
628
- if self.testing:
629
- instance = DependencyWrapper(
630
- interface=parameter.annotation, instance=instance
631
- )
632
- if parameter.kind == parameter.POSITIONAL_ONLY:
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, DependencyWrapper)
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=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 "
@@ -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
- self._parameters = [
213
- parameter.replace(
214
- annotation=get_typed_annotation(
215
- parameter.annotation,
216
- self._call_globals,
217
- module=self._call_module,
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
- for parameter in signature.parameters.values()
221
- ]
224
+ parameters.append(parameter.replace(annotation=annotation))
225
+ self._parameters = parameters
@@ -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
- @dataclass(frozen=True)
42
- class DependencyWrapper:
43
- interface: type[Any]
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 {"interface", "instance"}:
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
- @dataclass(frozen=True)
58
- class Dependency:
61
+ class Dependency(NamedTuple):
59
62
  member: Any
60
63
  module: ModuleType
61
64
 
@@ -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
- async def _anydi_ainject(
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 not inspect.iscoroutinefunction(request.function) or not _anydi_should_inject:
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
- # Setup the container
127
- container = cast(Container, request.getfixturevalue("anydi_setup_container"))
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
- for argname, interface in _anydi_injected_parameter_iterator():
130
- # Skip if the interface is not registered
131
- if container.strict and not container.is_registered(interface):
132
- continue
136
+ async def _awrapper() -> None:
137
+ # Setup the container
138
+ container = cast(Container, request.getfixturevalue("anydi_setup_container"))
133
139
 
134
- try:
135
- request.node.funcargs[argname] = await container.aresolve(interface)
136
- except Exception as exc:
137
- logger.warning(
138
- f"Failed to resolve dependency for argument '{argname}'.", exc_info=exc
139
- )
140
- _anydi_unresolved.append(interface)
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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "anydi"
3
- version = "0.34.1a1"
3
+ version = "0.36.0"
4
4
  description = "Dependency Injection library"
5
5
  authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
6
6
  license = "MIT"
@@ -1,3 +0,0 @@
1
- import logging
2
-
3
- logger = logging.getLogger("anydi")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes