anydi 0.26.8a0__py3-none-any.whl → 0.27.0a0__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
@@ -11,9 +11,11 @@ from contextvars import ContextVar
11
11
  from functools import wraps
12
12
  from typing import (
13
13
  Any,
14
+ AsyncContextManager,
14
15
  AsyncIterator,
15
16
  Awaitable,
16
17
  Callable,
18
+ ContextManager,
17
19
  Iterable,
18
20
  Iterator,
19
21
  Mapping,
@@ -41,7 +43,7 @@ from ._context import (
41
43
  from ._logger import logger
42
44
  from ._module import Module, ModuleRegistry
43
45
  from ._scanner import Scanner
44
- from ._types import AnyInterface, Event, Interface, Provider, Scope, is_marker
46
+ from ._types import AnyInterface, Interface, Provider, Scope, is_marker
45
47
  from ._utils import (
46
48
  get_full_qualname,
47
49
  get_typed_parameters,
@@ -84,7 +86,7 @@ class Container:
84
86
  strict: Whether to enable strict mode. Defaults to False.
85
87
  """
86
88
  self._providers: dict[type[Any], Provider] = {}
87
- self._resource_cache: dict[Scope, list[type[Any]]] = defaultdict(list)
89
+ self._providers_cache: dict[Scope, list[type[Any]]] = defaultdict(list)
88
90
  self._singleton_context = SingletonContext(self)
89
91
  self._transient_context = TransientContext(self)
90
92
  self._request_context_var: ContextVar[RequestContext | None] = ContextVar(
@@ -170,7 +172,7 @@ class Container:
170
172
 
171
173
  # Create Event type
172
174
  if provider.is_resource and (interface is NoneType or interface is None):
173
- interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
175
+ interface = type(f"Event_{uuid.uuid4().hex}", (), {})
174
176
 
175
177
  if interface in self._providers:
176
178
  if override:
@@ -223,7 +225,7 @@ class Container:
223
225
  scoped_context.delete(interface)
224
226
 
225
227
  # Cleanup provider references
226
- self._delete_provider(interface)
228
+ self._providers.pop(interface, None)
227
229
 
228
230
  def _get_provider(self, interface: AnyInterface) -> Provider:
229
231
  """Get provider by interface.
@@ -284,17 +286,7 @@ class Container:
284
286
  """
285
287
  self._providers[interface] = provider
286
288
  if provider.is_resource:
287
- self._resource_cache[provider.scope].append(interface)
288
-
289
- def _delete_provider(self, interface: AnyInterface) -> None:
290
- """Delete a provider by interface.
291
-
292
- Args:
293
- interface: The interface for which to delete the provider.
294
- """
295
- provider = self._providers.pop(interface, None)
296
- if provider is not None and provider.is_resource:
297
- self._resource_cache[provider.scope].remove(interface)
289
+ self._providers_cache[provider.scope].append(interface)
298
290
 
299
291
  def _validate_provider_scope(self, provider: Provider) -> None:
300
292
  """Validate the scope of a provider.
@@ -429,9 +421,9 @@ class Container:
429
421
  exc_type: type[BaseException] | None,
430
422
  exc_val: BaseException | None,
431
423
  exc_tb: types.TracebackType | None,
432
- ) -> bool:
424
+ ) -> None:
433
425
  """Exit the singleton context."""
434
- return self._singleton_context.__exit__(exc_type, exc_val, exc_tb)
426
+ self.close()
435
427
 
436
428
  def start(self) -> None:
437
429
  """Start the singleton context."""
@@ -441,13 +433,20 @@ class Container:
441
433
  """Close the singleton context."""
442
434
  self._singleton_context.close()
443
435
 
444
- @contextlib.contextmanager
445
- def request_context(self) -> Iterator[None]:
436
+ def request_context(self) -> ContextManager[None]:
446
437
  """Obtain a context manager for the request-scoped context.
447
438
 
448
439
  Returns:
449
440
  A context manager for the request-scoped context.
450
441
  """
442
+ return contextlib.contextmanager(self._request_context)()
443
+
444
+ def _request_context(self) -> Iterator[None]:
445
+ """Internal method that manages the request-scoped context.
446
+
447
+ Yields:
448
+ Yield control to the code block within the request context.
449
+ """
451
450
  context = RequestContext(self)
452
451
  token = self._request_context_var.set(context)
453
452
  with context:
@@ -464,25 +463,34 @@ class Container:
464
463
  exc_type: type[BaseException] | None,
465
464
  exc_val: BaseException | None,
466
465
  exc_tb: types.TracebackType | None,
467
- ) -> bool:
466
+ ) -> None:
468
467
  """Exit the singleton context."""
469
- return await self._singleton_context.__aexit__(exc_type, exc_val, exc_tb)
468
+ await self.aclose()
470
469
 
471
470
  async def astart(self) -> None:
472
471
  """Start the singleton context asynchronously."""
473
- await self._singleton_context.astart()
472
+ for interface, provider in self._providers.items():
473
+ if provider.scope == "singleton":
474
+ await self.aresolve(interface) # noqa
474
475
 
475
476
  async def aclose(self) -> None:
476
477
  """Close the singleton context asynchronously."""
477
478
  await self._singleton_context.aclose()
478
479
 
479
- @contextlib.asynccontextmanager
480
- async def arequest_context(self) -> AsyncIterator[None]:
480
+ def arequest_context(self) -> AsyncContextManager[None]:
481
481
  """Obtain an async context manager for the request-scoped context.
482
482
 
483
483
  Returns:
484
484
  An async context manager for the request-scoped context.
485
485
  """
486
+ return contextlib.asynccontextmanager(self._arequest_context)()
487
+
488
+ async def _arequest_context(self) -> AsyncIterator[None]:
489
+ """Internal method that manages the async request-scoped context.
490
+
491
+ Yields:
492
+ Yield control to the code block within the request context.
493
+ """
486
494
  context = RequestContext(self)
487
495
  token = self._request_context_var.set(context)
488
496
  async with context:
anydi/_context.py CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast
7
7
 
8
8
  from typing_extensions import Self, final
9
9
 
10
- from ._types import AnyInterface, Interface, Provider, Scope, is_event_type
10
+ from ._types import AnyInterface, Interface, Provider, Scope
11
11
  from ._utils import run_async
12
12
 
13
13
  if TYPE_CHECKING:
@@ -243,7 +243,7 @@ class ResourceScopedContext(ScopedContext):
243
243
  exc_type: type[BaseException] | None,
244
244
  exc_val: BaseException | None,
245
245
  exc_tb: TracebackType | None,
246
- ) -> bool:
246
+ ) -> None:
247
247
  """Exit the context.
248
248
 
249
249
  Args:
@@ -251,17 +251,17 @@ class ResourceScopedContext(ScopedContext):
251
251
  exc_val: The exception instance, if any.
252
252
  exc_tb: The traceback, if any.
253
253
  """
254
- return self._stack.__exit__(exc_type, exc_val, exc_tb)
254
+ self.close()
255
+ return
255
256
 
256
- @abc.abstractmethod
257
257
  def start(self) -> None:
258
258
  """Start the scoped context."""
259
- for interface in self.container._resource_cache.get(self.scope, []): # noqa
259
+ for interface in self.container._providers_cache.get(self.scope, []): # noqa
260
260
  self.container.resolve(interface)
261
261
 
262
262
  def close(self) -> None:
263
263
  """Close the scoped context."""
264
- self._stack.__exit__(None, None, None)
264
+ self._stack.close()
265
265
 
266
266
  async def __aenter__(self) -> Self:
267
267
  """Enter the context asynchronously.
@@ -277,7 +277,7 @@ class ResourceScopedContext(ScopedContext):
277
277
  exc_type: type[BaseException] | None,
278
278
  exc_val: BaseException | None,
279
279
  exc_tb: TracebackType | None,
280
- ) -> bool:
280
+ ) -> None:
281
281
  """Exit the context asynchronously.
282
282
 
283
283
  Args:
@@ -285,17 +285,18 @@ class ResourceScopedContext(ScopedContext):
285
285
  exc_val: The exception instance, if any.
286
286
  exc_tb: The traceback, if any.
287
287
  """
288
- return await run_async(
289
- self.__exit__, exc_type, exc_val, exc_tb
290
- ) or await self._async_stack.__aexit__(exc_type, exc_val, exc_tb)
288
+ await self.aclose()
289
+ return
291
290
 
292
- @abc.abstractmethod
293
291
  async def astart(self) -> None:
294
292
  """Start the scoped context asynchronously."""
293
+ for interface in self.container._providers_cache.get(self.scope, []): # noqa
294
+ await self.container.aresolve(interface)
295
295
 
296
296
  async def aclose(self) -> None:
297
297
  """Close the scoped context asynchronously."""
298
- await self.__aexit__(None, None, None)
298
+ await run_async(self._stack.close)
299
+ await self._async_stack.aclose()
299
300
 
300
301
 
301
302
  @final
@@ -304,16 +305,6 @@ class SingletonContext(ResourceScopedContext):
304
305
 
305
306
  scope = "singleton"
306
307
 
307
- def start(self) -> None:
308
- """Start the scoped context."""
309
- for interface in self.container._resource_cache.get(self.scope, []): # noqa
310
- self.container.resolve(interface)
311
-
312
- async def astart(self) -> None:
313
- """Start the scoped context asynchronously."""
314
- for interface in self.container._resource_cache.get(self.scope, []): # noqa
315
- await self.container.aresolve(interface)
316
-
317
308
 
318
309
  @final
319
310
  class RequestContext(ResourceScopedContext):
@@ -321,20 +312,6 @@ class RequestContext(ResourceScopedContext):
321
312
 
322
313
  scope = "request"
323
314
 
324
- def start(self) -> None:
325
- """Start the scoped context."""
326
- for interface in self.container._resource_cache.get(self.scope, []): # noqa
327
- if not is_event_type(interface):
328
- continue
329
- self.container.resolve(interface)
330
-
331
- async def astart(self) -> None:
332
- """Start the scoped context asynchronously."""
333
- for interface in self.container._resource_cache.get(self.scope, []): # noqa
334
- if not is_event_type(interface):
335
- continue
336
- await self.container.aresolve(interface)
337
-
338
315
 
339
316
  @final
340
317
  class TransientContext(ScopedContext):
anydi/_types.py CHANGED
@@ -30,17 +30,6 @@ def is_marker(obj: Any) -> bool:
30
30
  return isinstance(obj, Marker)
31
31
 
32
32
 
33
- class Event:
34
- """Represents an event object."""
35
-
36
- __slots__ = ()
37
-
38
-
39
- def is_event_type(obj: Any) -> bool:
40
- """Checks if an object is an event type."""
41
- return inspect.isclass(obj) and issubclass(obj, Event)
42
-
43
-
44
33
  @dataclass(frozen=True)
45
34
  class Provider:
46
35
  """Represents a provider object.
anydi/_utils.py CHANGED
@@ -6,7 +6,7 @@ import builtins
6
6
  import functools
7
7
  import inspect
8
8
  import sys
9
- from typing import Any, AsyncIterator, Callable, ForwardRef, Iterator, TypeVar
9
+ from typing import Any, AsyncIterator, Callable, ForwardRef, Iterator, TypeVar, cast
10
10
 
11
11
  from typing_extensions import ParamSpec, get_args, get_origin
12
12
 
@@ -16,18 +16,19 @@ except ImportError:
16
16
  anyio = None # type: ignore[assignment]
17
17
 
18
18
 
19
- T = TypeVar("T")
20
- P = ParamSpec("P")
19
+ if sys.version_info < (3, 9): # pragma: nocover
21
20
 
22
-
23
- def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
24
- if sys.version_info < (3, 9):
21
+ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
25
22
  return type_._evaluate(globalns, localns) # noqa
26
- elif sys.version_info >= (3, 12):
27
- return type_._evaluate( # noqa
28
- globalns, localns, frozenset(), recursive_guard=frozenset()
29
- )
30
- return type_._evaluate(globalns, localns, frozenset()) # noqa
23
+
24
+ else:
25
+
26
+ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
27
+ return cast(Any, type_)._evaluate(globalns, localns, set()) # noqa
28
+
29
+
30
+ T = TypeVar("T")
31
+ P = ParamSpec("P")
31
32
 
32
33
 
33
34
  def get_full_qualname(obj: Any) -> str:
@@ -69,12 +70,10 @@ def get_typed_annotation(
69
70
  ) -> Any:
70
71
  """Get the typed annotation of a parameter."""
71
72
  if isinstance(annotation, str):
72
- if sys.version_info >= (3, 10, 2):
73
- annotation = ForwardRef(annotation, module=module, is_class=is_class)
74
- elif sys.version_info >= (3, 10, 0):
75
- annotation = ForwardRef(annotation, module=module)
76
- else:
73
+ if sys.version_info < (3, 9):
77
74
  annotation = ForwardRef(annotation)
75
+ else:
76
+ annotation = ForwardRef(annotation, module=module, is_class=is_class)
78
77
  annotation = evaluate_forwardref(annotation, globalns, {})
79
78
  return annotation
80
79
 
anydi/ext/_utils.py ADDED
@@ -0,0 +1,89 @@
1
+ """AnyDI FastAPI extension."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import logging
7
+ from typing import Any, Callable
8
+
9
+ from typing_extensions import Annotated, get_args, get_origin
10
+
11
+ from anydi import Container
12
+ from anydi._utils import get_full_qualname
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class HasInterface:
18
+ _interface: Any = None
19
+
20
+ @property
21
+ def interface(self) -> Any:
22
+ if self._interface is None:
23
+ raise TypeError("Interface is not set.")
24
+ return self._interface
25
+
26
+ @interface.setter
27
+ def interface(self, interface: Any) -> None:
28
+ self._interface = interface
29
+
30
+
31
+ def patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
32
+ """Patch an annotated parameter to resolve the default value."""
33
+ if not (
34
+ get_origin(parameter.annotation) is Annotated
35
+ and parameter.default is parameter.empty
36
+ ):
37
+ return parameter
38
+
39
+ tp_origin, *tp_metadata = get_args(parameter.annotation)
40
+ default = tp_metadata[-1]
41
+
42
+ if not isinstance(default, HasInterface):
43
+ return parameter
44
+
45
+ if (num := len(tp_metadata[:-1])) == 0:
46
+ interface = tp_origin
47
+ elif num == 1:
48
+ interface = Annotated[tp_origin, tp_metadata[0]]
49
+ elif num == 2:
50
+ interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
51
+ elif num == 3:
52
+ interface = Annotated[
53
+ tp_origin,
54
+ tp_metadata[0],
55
+ tp_metadata[1],
56
+ tp_metadata[2],
57
+ ]
58
+ else:
59
+ raise TypeError("Too many annotated arguments.") # pragma: no cover
60
+ return parameter.replace(annotation=interface, default=default)
61
+
62
+
63
+ def patch_call_parameter(
64
+ call: Callable[..., Any], parameter: inspect.Parameter, container: Container
65
+ ) -> None:
66
+ """Patch a parameter to inject dependencies using AnyDI.
67
+
68
+ Args:
69
+ call: The call function.
70
+ parameter: The parameter to patch.
71
+ container: The AnyDI container.
72
+ """
73
+ parameter = patch_annotated_parameter(parameter)
74
+
75
+ if not isinstance(parameter.default, HasInterface):
76
+ return None
77
+
78
+ if not container.strict and not container.is_registered(parameter.annotation):
79
+ logger.debug(
80
+ f"Callable `{get_full_qualname(call)}` injected parameter "
81
+ f"`{parameter.name}` with an annotation of "
82
+ f"`{get_full_qualname(parameter.annotation)}` "
83
+ "is not registered. It will be registered at runtime with the "
84
+ "first call because it is running in non-strict mode."
85
+ )
86
+ else:
87
+ container._validate_injected_parameter(call, parameter) # noqa
88
+
89
+ parameter.default.interface = parameter.annotation
anydi/ext/fastapi.py CHANGED
@@ -2,19 +2,18 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import inspect
6
5
  import logging
7
- from typing import Any, Callable, Iterator, cast
6
+ from typing import Any, Iterator, cast
8
7
 
9
8
  from fastapi import Depends, FastAPI, params
10
9
  from fastapi.dependencies.models import Dependant
11
10
  from fastapi.routing import APIRoute
12
11
  from starlette.requests import Request
13
- from typing_extensions import Annotated, get_args, get_origin
14
12
 
15
13
  from anydi import Container
16
- from anydi._utils import get_full_qualname, get_typed_parameters
14
+ from anydi._utils import get_typed_parameters
17
15
 
16
+ from ._utils import HasInterface, patch_call_parameter
18
17
  from .starlette.middleware import RequestScopedMiddleware
19
18
 
20
19
  __all__ = ["RequestScopedMiddleware", "install", "get_container", "Inject"]
@@ -48,7 +47,7 @@ def install(app: FastAPI, container: Container) -> None:
48
47
  if not call:
49
48
  continue # pragma: no cover
50
49
  for parameter in get_typed_parameters(call):
51
- _patch_route_parameter(call, parameter, container)
50
+ patch_call_parameter(call, parameter, container)
52
51
 
53
52
 
54
53
  def get_container(request: Request) -> Container:
@@ -63,22 +62,11 @@ def get_container(request: Request) -> Container:
63
62
  return cast(Container, request.app.state.container)
64
63
 
65
64
 
66
- class Resolver(params.Depends):
65
+ class Resolver(HasInterface, params.Depends):
67
66
  """Parameter dependency class for injecting dependencies using AnyDI."""
68
67
 
69
68
  def __init__(self) -> None:
70
69
  super().__init__(dependency=self._dependency, use_cache=True)
71
- self._interface: Any = None
72
-
73
- @property
74
- def interface(self) -> Any:
75
- if self._interface is None:
76
- raise TypeError("Interface is not set.")
77
- return self._interface
78
-
79
- @interface.setter
80
- def interface(self, interface: Any) -> None:
81
- self._interface = interface
82
70
 
83
71
  async def _dependency(self, container: Container = Depends(get_container)) -> Any:
84
72
  return await container.aresolve(self.interface)
@@ -102,64 +90,3 @@ def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
102
90
  if dependant.dependencies:
103
91
  for sub_dependant in dependant.dependencies:
104
92
  yield from _iter_dependencies(sub_dependant)
105
-
106
-
107
- def _patch_route_parameter(
108
- call: Callable[..., Any], parameter: inspect.Parameter, container: Container
109
- ) -> None:
110
- """Patch a parameter to inject dependencies using AnyDI.
111
-
112
- Args:
113
- call: The call function.
114
- parameter: The parameter to patch.
115
- container: The AnyDI container.
116
- """
117
- parameter = _patch_annotated_parameter(parameter)
118
-
119
- if not isinstance(parameter.default, Resolver):
120
- return None
121
-
122
- if not container.strict and not container.is_registered(parameter.annotation):
123
- logger.debug(
124
- f"Callable `{get_full_qualname(call)}` injected parameter "
125
- f"`{parameter.name}` with an annotation of "
126
- f"`{get_full_qualname(parameter.annotation)}` "
127
- "is not registered. It will be registered at runtime with the "
128
- "first call because it is running in non-strict mode."
129
- )
130
- else:
131
- container._validate_injected_parameter(call, parameter) # noqa
132
-
133
- parameter.default.interface = parameter.annotation
134
-
135
-
136
- def _patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
137
- """Patch an annotated parameter to resolve the default value."""
138
- if not (
139
- get_origin(parameter.annotation) is Annotated
140
- and parameter.default is parameter.empty
141
- ):
142
- return parameter
143
-
144
- tp_origin, *tp_metadata = get_args(parameter.annotation)
145
- default = tp_metadata[-1]
146
-
147
- if not isinstance(default, Resolver):
148
- return parameter
149
-
150
- if (num := len(tp_metadata[:-1])) == 0:
151
- interface = tp_origin
152
- elif num == 1:
153
- interface = Annotated[tp_origin, tp_metadata[0]]
154
- elif num == 2:
155
- interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
156
- elif num == 3:
157
- interface = Annotated[
158
- tp_origin,
159
- tp_metadata[0],
160
- tp_metadata[1],
161
- tp_metadata[2],
162
- ]
163
- else:
164
- raise TypeError("Too many annotated arguments.") # pragma: no cover
165
- return parameter.replace(annotation=interface, default=default)
@@ -0,0 +1,50 @@
1
+ import logging
2
+ from typing import Any, cast
3
+
4
+ from fast_depends.dependencies import Depends
5
+ from faststream import ContextRepo
6
+ from faststream.broker.core.usecase import BrokerUsecase
7
+
8
+ from anydi import Container
9
+ from anydi._utils import get_typed_parameters
10
+
11
+ from ._utils import HasInterface, patch_call_parameter
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
17
+ """Install AnyDI into a FastStream broker.
18
+
19
+ Args:
20
+ broker: The broker.
21
+ container: The container.
22
+
23
+ This function installs the AnyDI container into a FastStream broker by attaching
24
+ it to the broker. It also patches the broker handlers to inject the required
25
+ dependencies using AnyDI.
26
+ """
27
+ broker._container = container # type: ignore[attr-defined]
28
+
29
+ for subscriber in broker._subscribers.values(): # noqa
30
+ call = subscriber.calls[0].handler._original_call # noqa
31
+ for parameter in get_typed_parameters(call):
32
+ patch_call_parameter(call, parameter, container)
33
+
34
+
35
+ def get_container(broker: BrokerUsecase[Any, Any]) -> Container:
36
+ return cast(Container, getattr(broker, "_container")) # noqa
37
+
38
+
39
+ class Resolver(HasInterface, Depends):
40
+ """Parameter dependency class for injecting dependencies using AnyDI."""
41
+
42
+ def __init__(self) -> None:
43
+ super().__init__(dependency=self._dependency, use_cache=True, cast=True)
44
+
45
+ async def _dependency(self, context: ContextRepo) -> Any:
46
+ return get_container(context.get("broker")).resolve(self.interface)
47
+
48
+
49
+ def Inject() -> Any: # noqa
50
+ return Resolver()
@@ -63,15 +63,12 @@ def _anydi_injected_parameter_iterator(
63
63
  request: pytest.FixtureRequest,
64
64
  _anydi_unresolved: list[str],
65
65
  ) -> Callable[[], Iterator[tuple[str, Any]]]:
66
- registered_fixtures = request.session._fixturemanager._arg2fixturedefs # noqa
67
-
68
66
  def _iterator() -> Iterator[tuple[str, inspect.Parameter]]:
69
67
  for parameter in get_typed_parameters(request.function):
70
- interface = parameter.annotation
71
68
  if (
72
- interface is parameter.empty
69
+ ((interface := parameter.annotation) is parameter.empty)
73
70
  or interface in _anydi_unresolved
74
- or parameter.name in registered_fixtures
71
+ or parameter.name in request.node.funcargs
75
72
  ):
76
73
  continue
77
74
  yield parameter.name, interface
@@ -95,14 +92,18 @@ def _anydi_inject(
95
92
  container = cast(Container, request.getfixturevalue("anydi_setup_container"))
96
93
 
97
94
  for argname, interface in _anydi_injected_parameter_iterator():
98
- # Skip if the interface is not registered
99
- if container.strict and not container.is_registered(interface):
100
- continue
101
-
102
95
  try:
103
- request.node.funcargs[argname] = container.resolve(interface)
104
- except Exception: # noqa
96
+ # Release the instance if it was already resolved
97
+ container.release(interface)
98
+ except LookupError:
99
+ pass
100
+ try:
101
+ # Resolve the instance
102
+ instance = container.resolve(interface)
103
+ except LookupError:
105
104
  _anydi_unresolved.append(interface)
105
+ continue
106
+ request.node.funcargs[argname] = instance
106
107
 
107
108
 
108
109
  @pytest.fixture(autouse=True)
@@ -120,11 +121,15 @@ async def _anydi_ainject(
120
121
  container = cast(Container, request.getfixturevalue("anydi_setup_container"))
121
122
 
122
123
  for argname, interface in _anydi_injected_parameter_iterator():
123
- # Skip if the interface is not registered
124
- if container.strict and not container.is_registered(interface):
125
- continue
126
-
127
124
  try:
128
- request.node.funcargs[argname] = await container.aresolve(interface)
129
- except Exception: # noqa
125
+ # Release the instance if it was already resolved
126
+ container.release(interface)
127
+ except LookupError:
128
+ pass
129
+ try:
130
+ # Resolve the instance
131
+ instance = await container.aresolve(interface)
132
+ except LookupError:
130
133
  _anydi_unresolved.append(interface)
134
+ continue
135
+ request.node.funcargs[argname] = instance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anydi
3
- Version: 0.26.8a0
3
+ Version: 0.27.0a0
4
4
  Summary: Dependency Injection library
5
5
  Home-page: https://github.com/antonrh/anydi
6
6
  License: MIT
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.7
25
26
  Classifier: Topic :: Internet
26
27
  Classifier: Topic :: Software Development
27
28
  Classifier: Topic :: Software Development :: Libraries
@@ -1,12 +1,13 @@
1
1
  anydi/__init__.py,sha256=aeaBp5vq09sG-e9sqqs9qpUtUIDNfOdFPrlAfE5Ku9E,584
2
- anydi/_container.py,sha256=-vpiYl2Tx2QK9wl74DiF5qLFRudJ3e78TqEyuyOrthE,29055
3
- anydi/_context.py,sha256=qqaPVv-nnpHQL5R9wFL6-709eyAt9ZnzZov79NPEHIc,11865
2
+ anydi/_container.py,sha256=IhfucOoAPYpMY9A0uHcqa1EOzE3_0TsExR-ZehZrZ6U,29246
3
+ anydi/_context.py,sha256=e0VX0fiflzW_2O9w3HvUH3YRCwXHsruQjf3Lu-zXgDw,10815
4
4
  anydi/_logger.py,sha256=UpubJUnW83kffFxkhUlObm2DmZX1Pjqoz9YFKS-JOPg,52
5
5
  anydi/_module.py,sha256=E1TfLud_Af-MPB83PxIzHVA1jlDW2FGaRP_il1a6y3Y,3675
6
6
  anydi/_scanner.py,sha256=cyEk-K2Q8ssZStq8GrxMeEcCuAZMw-RXrjlgWEevKCs,6667
7
- anydi/_types.py,sha256=i8xFxz8pmFj7SGqwOwae_P9VtiRie6DVLwfaLibLwhc,3653
8
- anydi/_utils.py,sha256=TzUZRTNaXu78Uzv9NCNLSM5s9TgEIiXtXgr1HhsfHac,4017
7
+ anydi/_types.py,sha256=vQTrFjsYhlMxfo1nOFem05x2QUJMQkVh4ZaC7W0XZJY,3434
8
+ anydi/_utils.py,sha256=zP4UvO1aVQJTB8pFNUWAcncvSiuhcg4xNdRU7CoLrqw,3871
9
9
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ anydi/ext/_utils.py,sha256=2kxLPTMM9Ro3s6-knbqYzONlqRB3hMcwZFFRQGHcFUg,2691
10
11
  anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
11
12
  anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
12
13
  anydi/ext/django/_settings.py,sha256=cKzFBGtPCsexZ2ZcInubBukIebhxzNfa3F0KuwoZYaA,844
@@ -16,13 +17,14 @@ anydi/ext/django/middleware.py,sha256=iVHWtE829khMY-BXbNNt0g2FrIApKprna7dCG9ObEi
16
17
  anydi/ext/django/ninja/__init__.py,sha256=kW3grUgWp_nkWSG_-39ADHMrZLGNcj9TsJ9OW8iWWrk,546
17
18
  anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI8W-oO2w,2696
18
19
  anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfyk_NVEWw,2207
19
- anydi/ext/fastapi.py,sha256=kVUKVKtqCx1Nfnm1oh2BMyB0G7qQKPw6OGfxFlqUqtc,5305
20
- anydi/ext/pytest_plugin.py,sha256=3OWphc4nEzla46_8KR7LXtwGns5eol_YlUWfTf4Cr2Q,3952
20
+ anydi/ext/fastapi.py,sha256=vhfSyovXuCjvSkx6AiLOTNU975i8wDg72C5fqXQiFLw,2896
21
+ anydi/ext/faststream.py,sha256=svMtqFVSRTpuf4H5yeozHWmrBXi_F8cevb5d2mo3m-E,1617
22
+ anydi/ext/pytest_plugin.py,sha256=nDNqjblVQufLha6P8J8hZw4Q0EuwC71bOI_bdaGm-OQ,4043
21
23
  anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
24
  anydi/ext/starlette/middleware.py,sha256=Ni0BQaPjs_Ha6zcLZYYJ3-XkslTCnL9aCSa06rnRDMI,1139
23
25
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- anydi-0.26.8a0.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
25
- anydi-0.26.8a0.dist-info/METADATA,sha256=S3mIlHTg2I5Oy4kQm8wKb-iQRgFHa9wbdmLadWQPAes,5112
26
- anydi-0.26.8a0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
27
- anydi-0.26.8a0.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
28
- anydi-0.26.8a0.dist-info/RECORD,,
26
+ anydi-0.27.0a0.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
27
+ anydi-0.27.0a0.dist-info/METADATA,sha256=sVLcw-YRcQ7PD1C39knmNRgN0gbvoQlqzfU2pF7NpkM,5162
28
+ anydi-0.27.0a0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
29
+ anydi-0.27.0a0.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
30
+ anydi-0.27.0a0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.8.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any