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 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, Awaitable, Iterable, Iterator, Sequence
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 ._injector import Injector
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
- sub_provider = self._get_or_register_provider(parameter.annotation)
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
- return cast(T, scoped_context.get(provider))
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
- return cast(T, await scoped_context.aget(provider))
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 | Awaitable[T]] | None = None
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
- inner: Callable[P, T | Awaitable[T]],
436
- ) -> Callable[P, T | Awaitable[T]]:
437
- return self._injector.inject(inner)
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 cast(T, self._injector.inject(func)(*args, **kwargs))
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 get(self, provider: Provider) -> Any:
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 aget(self, provider: Provider) -> Any:
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._get_provider_params(provider)
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._aget_provider_params(provider)
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 _get_provider_params(
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
- instance = self._resolve_parameter(provider, parameter)
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 _aget_provider_params(
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
- instance = await self._aresolve_parameter(provider, parameter)
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 get(self, provider: Provider) -> Any:
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
- return instance
166
+ return instance, True
167
+ return instance, False
147
168
 
148
- async def aget(self, provider: Provider) -> Any:
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
- return instance
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._get_provider_params(provider)
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._aget_provider_params(provider)
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 get(self, provider: Provider) -> Any:
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 aget(self, provider: Provider) -> Any:
295
- """Get an async instance of a dependency from the transient context."""
296
- return await self._acreate_instance(provider)
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
@@ -69,7 +69,7 @@ class Provider:
69
69
 
70
70
  def __eq__(self, other: object) -> bool:
71
71
  if not isinstance(other, Provider):
72
- return NotImplemented
72
+ return NotImplemented # pragma: no cover
73
73
  return (
74
74
  self._call == other._call
75
75
  and self._scope == other._scope
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, container: Container
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._injector._validate_injected_parameter(call, parameter) # noqa
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, container)
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, container)
29
+ patch_call_parameter(container, call, parameter)
30
30
 
31
31
 
32
32
  def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
@@ -105,8 +105,10 @@ def _anydi_inject(
105
105
 
106
106
  try:
107
107
  request.node.funcargs[argname] = container.resolve(interface)
108
- except Exception: # noqa
109
- logger.warning(f"Failed to resolve dependency for argument '{argname}'.")
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: # noqa
135
- logger.warning(f"Failed to resolve dependency for argument '{argname}'.")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anydi
3
- Version: 0.32.2
3
+ Version: 0.33.1
4
4
  Summary: Dependency Injection library
5
5
  Home-page: https://github.com/antonrh/anydi
6
6
  License: MIT
@@ -1,15 +1,14 @@
1
1
  anydi/__init__.py,sha256=EsR-HiMe8cWS9PQbY23ibc91STK1WTn02DFMPV-TNU4,509
2
- anydi/_container.py,sha256=gBcdz7wplCm-H8sj_QZpQsgISsg-B9RL6LuZ2kR_PdU,16925
3
- anydi/_context.py,sha256=XSXBpSCm6zwR5otLN2cj_mPDQXTM18aN6vmJhiNEoL4,11697
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=-CJXDvAEdV2IX--0XWAFhAs_LH_9cGp9XhhrrczuMgM,6193
6
+ anydi/_provider.py,sha256=w_GnRo324aqNORRJwuURexA54c1M3smj34Q8EaV0QGE,6213
8
7
  anydi/_scanner.py,sha256=F2sHgJvkRYXYnu4F5iSrnIPVzwnNeS7tRPXziirh4NI,4898
9
- anydi/_types.py,sha256=At2VeJgwNC0gwMqdL0tkGtjSFsqOdrQStDwyzAGugr4,796
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=PHOLjnohC4gbhZESDnnvuup25XTWmQzDaS2y-9SYzaw,2576
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=ebw6-352rMk_YpwPQrL8cUEK0XxGuxDAYCLfHwuDGTQ,2436
23
- anydi/ext/faststream.py,sha256=asH5Cup5Uu_mG95_OkJZYeLbJ1ttkVQHPRdQTTJmjGE,1898
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=Df0pFgAOk-44UFsdZGAOSxElJTJLchs4sk2UZuV-KVk,4212
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.32.2.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
30
- anydi-0.32.2.dist-info/METADATA,sha256=gZZq6SbwsHOAgcT-gx9Y4SoG52zEW6EQPVbmZMCLAH8,5112
31
- anydi-0.32.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
32
- anydi-0.32.2.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
33
- anydi-0.32.2.dist-info/RECORD,,
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