anydi 0.40.0__py3-none-any.whl → 0.42.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/testing.py ADDED
@@ -0,0 +1,172 @@
1
+ import contextlib
2
+ import inspect
3
+ import logging
4
+ from collections.abc import Iterable, Iterator, Sequence
5
+ from typing import Any, TypeVar, cast
6
+
7
+ import wrapt # type: ignore
8
+ from typing_extensions import Self
9
+
10
+ from ._container import Container
11
+ from ._context import InstanceContext
12
+ from ._module import ModuleDef
13
+ from ._provider import Provider, ProviderDef
14
+ from ._scope import Scope
15
+ from ._typing import type_repr
16
+
17
+ T = TypeVar("T")
18
+
19
+
20
+ class TestContainer(Container):
21
+ def __init__(
22
+ self,
23
+ *,
24
+ providers: Sequence[ProviderDef] | None = None,
25
+ modules: Iterable[ModuleDef] | None = None,
26
+ strict: bool = False,
27
+ default_scope: Scope = "transient",
28
+ logger: logging.Logger | None = None,
29
+ ) -> None:
30
+ super().__init__(
31
+ providers=providers,
32
+ modules=modules,
33
+ strict=strict,
34
+ default_scope=default_scope,
35
+ logger=logger,
36
+ )
37
+ self._override_instances: dict[Any, Any] = {}
38
+
39
+ @classmethod
40
+ def from_container(cls, container: Container) -> Self:
41
+ return cls(
42
+ providers=[
43
+ ProviderDef(
44
+ interface=provider.interface,
45
+ call=provider.call,
46
+ scope=provider.scope,
47
+ )
48
+ for provider in container.providers.values()
49
+ ],
50
+ strict=container.strict,
51
+ default_scope=container.default_scope,
52
+ logger=container.logger,
53
+ )
54
+
55
+ @contextlib.contextmanager
56
+ def override(self, interface: Any, instance: Any) -> Iterator[None]:
57
+ """
58
+ Override the provider for the specified interface with a specific instance.
59
+ """
60
+ if not self.is_registered(interface) and self.strict:
61
+ raise LookupError(
62
+ f"The provider interface `{type_repr(interface)}` not registered."
63
+ )
64
+ self._override_instances[interface] = instance
65
+ try:
66
+ yield
67
+ finally:
68
+ self._override_instances.pop(interface, None)
69
+
70
+ def _resolve_or_create(
71
+ self, interface: type[T], create: bool, /, **defaults: Any
72
+ ) -> T:
73
+ """Internal method to handle instance resolution and creation."""
74
+ instance = super()._resolve_or_create(interface, create, **defaults)
75
+ return cast(T, self._patch_resolver(interface, instance))
76
+
77
+ async def _aresolve_or_create(
78
+ self, interface: type[T], create: bool, /, **defaults: Any
79
+ ) -> T:
80
+ """Internal method to handle instance resolution and creation asynchronously."""
81
+ instance = await super()._aresolve_or_create(interface, create, **defaults)
82
+ return cast(T, self._patch_resolver(interface, instance))
83
+
84
+ def _get_provider_instance(
85
+ self,
86
+ provider: Provider,
87
+ parameter: inspect.Parameter,
88
+ context: InstanceContext | None,
89
+ /,
90
+ **defaults: Any,
91
+ ) -> Any:
92
+ """Retrieve an instance of a dependency from the scoped context."""
93
+ instance, is_default = super()._get_provider_instance(
94
+ provider, parameter, context, **defaults
95
+ )
96
+ if not is_default:
97
+ instance = InstanceProxy(instance, interface=parameter.annotation)
98
+ return instance, is_default
99
+
100
+ async def _aget_provider_instance(
101
+ self,
102
+ provider: Provider,
103
+ parameter: inspect.Parameter,
104
+ context: InstanceContext | None,
105
+ /,
106
+ **defaults: Any,
107
+ ) -> Any:
108
+ """Asynchronously retrieve an instance of a dependency from the context."""
109
+ instance, is_default = await super()._aget_provider_instance(
110
+ provider, parameter, context, **defaults
111
+ )
112
+ if not is_default:
113
+ instance = InstanceProxy(instance, interface=parameter.annotation)
114
+ return instance, is_default
115
+
116
+ def _patch_resolver(self, interface: type[Any], instance: Any) -> Any:
117
+ """Patch the test resolver for the instance."""
118
+ if interface in self._override_instances:
119
+ return self._override_instances[interface]
120
+
121
+ if not hasattr(instance, "__dict__") or hasattr(
122
+ instance, "__resolver_getter__"
123
+ ):
124
+ return instance
125
+
126
+ wrapped = {
127
+ name: value.interface
128
+ for name, value in instance.__dict__.items()
129
+ if isinstance(value, InstanceProxy)
130
+ }
131
+
132
+ def __resolver_getter__(name: str) -> Any:
133
+ if name in wrapped:
134
+ _interface = wrapped[name]
135
+ # Resolve the dependency if it's wrapped
136
+ return self.resolve(_interface)
137
+ raise LookupError
138
+
139
+ # Attach the resolver getter to the instance
140
+ instance.__resolver_getter__ = __resolver_getter__
141
+
142
+ if not hasattr(instance.__class__, "__getattribute_patched__"):
143
+
144
+ def __getattribute__(_self: Any, name: str) -> Any:
145
+ # Skip the resolver getter
146
+ if name in {"__resolver_getter__", "__class__"}:
147
+ return object.__getattribute__(_self, name)
148
+
149
+ if hasattr(_self, "__resolver_getter__"):
150
+ try:
151
+ return _self.__resolver_getter__(name)
152
+ except LookupError:
153
+ pass
154
+
155
+ # Fall back to default behavior
156
+ return object.__getattribute__(_self, name)
157
+
158
+ # Apply the patched resolver if wrapped attributes exist
159
+ instance.__class__.__getattribute__ = __getattribute__
160
+ instance.__class__.__getattribute_patched__ = True
161
+
162
+ return instance
163
+
164
+
165
+ class InstanceProxy(wrapt.ObjectProxy): # type: ignore
166
+ def __init__(self, wrapped: Any, *, interface: type[Any]) -> None:
167
+ super().__init__(wrapped) # type: ignore
168
+ self._self_interface = interface
169
+
170
+ @property
171
+ def interface(self) -> type[Any]:
172
+ return self._self_interface
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.40.0
3
+ Version: 0.42.0
4
4
  Summary: Dependency Injection library
5
5
  Project-URL: Repository, https://github.com/antonrh/anydi
6
6
  Author-email: Anton Ruhlov <antonruhlov@gmail.com>
@@ -29,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
29
  Classifier: Typing :: Typed
30
30
  Requires-Python: ~=3.9
31
31
  Requires-Dist: anyio>=3.7.1
32
- Requires-Dist: typing-extensions<5,>=4.13.2
32
+ Requires-Dist: typing-extensions<5,>=4.14.0
33
33
  Requires-Dist: wrapt<2,>=1.17.0
34
34
  Description-Content-Type: text/markdown
35
35
 
@@ -0,0 +1,34 @@
1
+ anydi/__init__.py,sha256=UH3N0LIw4ysoV0_EFgtDNioxEQxOmeytYbJnS8MXIXM,532
2
+ anydi/_async.py,sha256=KhRd3RmZFcwNDzrMm8ctA1gwrg-6m_7laECTYsZdF5k,1445
3
+ anydi/_container.py,sha256=Ap8PnH6ym7GTjBv9r2cmJkJCEY5XpzMXIkJYXXwJkrY,31611
4
+ anydi/_context.py,sha256=_Xy8cTpRskb4cxTd-Fe-5NnIZyBe1DnovkofhdeUfmw,3020
5
+ anydi/_decorators.py,sha256=b5FPT86pnJX8moKGaOgMclZYNXIkLhWXB_7jg1nogX0,2110
6
+ anydi/_module.py,sha256=DK--YVzvK1KpCbqJnfdB99_vEcjsqS6Xhqk7NNClqGs,2596
7
+ anydi/_provider.py,sha256=ig2ecn-STmFGcpkLE5A5OM35XHtU2NsxFVrGp2CvuvM,2123
8
+ anydi/_scan.py,sha256=ADA_gjsVLP7q43qU03bsTchEJwbpckknmDCLLMf7wss,3623
9
+ anydi/_scope.py,sha256=PFHjPb2-n0vhRo9mvD_craTFfoJBzR3y-N3_0apL5Q0,258
10
+ anydi/_typing.py,sha256=m7KnVenDE1E420IeYz2Ocw6dhddmSFbuBBgkOXtl9pA,3709
11
+ anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ anydi/testing.py,sha256=O6d6s5s7RET4CDit0ubvnV0TRuC37Z26-FV9cssZkC4,5963
13
+ anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ anydi/ext/_utils.py,sha256=FULK0KKLzWBQTYaUML_UCOpdCWSK5imMzzHHlOrGqjk,2554
15
+ anydi/ext/fastapi.py,sha256=q7q1hF5v4dpV3YM4L1CVuD20bXC7f5S7e1apAfekkG0,2448
16
+ anydi/ext/faststream.py,sha256=DLZTfiGeZJCiwfmZTvVecaTlX84_jqClh6cgY2qMPJg,1910
17
+ anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
18
+ anydi/ext/pytest_plugin.py,sha256=57Y1G4MOpwV43Fib88JGeRI8jACcj5Jy8R-7ZT0SZJs,4772
19
+ anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
20
+ anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
21
+ anydi/ext/django/_settings.py,sha256=Z0RlAuXoO73oahWeMkK10w8c-4uCBde-DBpeKTV5USY,853
22
+ anydi/ext/django/_utils.py,sha256=sYibOAo2ildVLppIM5EFI_SyGmJy4_IGJLuzKMYvLgs,3933
23
+ anydi/ext/django/apps.py,sha256=mfOLUQ1i-B4e3f5NPitbRVuVUlm4QoFzuJVXsI8zGWA,2791
24
+ anydi/ext/django/middleware.py,sha256=5OUdp0OWRozyW338Sq04BDhacaFlyUTTOduS_7EwCTA,854
25
+ anydi/ext/django/ninja/__init__.py,sha256=4J0zoHPK9itbTVrjjvLX6Ftrsb2ND8bITqNDIJzEhks,520
26
+ anydi/ext/django/ninja/_operation.py,sha256=wk5EOjLY3KVIHk9jMCGsFsja9-dQmMOpLpHXciqxQdk,2680
27
+ anydi/ext/django/ninja/_signature.py,sha256=UVLmKpYvH1fNb9C7ffP50KCmh0BE6unHegfss5dvpVU,2261
28
+ anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ anydi/ext/starlette/middleware.py,sha256=MxnzshAs-CMvjJp0r457k52MzBL8O4KAuClnF6exBdU,803
30
+ anydi-0.42.0.dist-info/METADATA,sha256=1Di-jvpUvC5xYuUb7T1GlcJbN3X0TepPAwaquSmc5HI,4957
31
+ anydi-0.42.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
32
+ anydi-0.42.0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
33
+ anydi-0.42.0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
34
+ anydi-0.42.0.dist-info/RECORD,,
anydi/_types.py DELETED
@@ -1,145 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import enum
4
- import inspect
5
- from collections.abc import Iterable
6
- from dataclasses import dataclass
7
- from functools import cached_property
8
- from types import ModuleType
9
- from typing import Annotated, Any, Callable, NamedTuple, Union
10
-
11
- import wrapt # type: ignore
12
- from typing_extensions import Literal, Self, TypeAlias
13
-
14
- Scope = Literal["transient", "singleton", "request"]
15
-
16
- AnyInterface: TypeAlias = Union[type[Any], Annotated[Any, ...]]
17
-
18
- NOT_SET = object()
19
-
20
-
21
- class Marker:
22
- """A marker class for marking dependencies."""
23
-
24
- __slots__ = ()
25
-
26
- def __call__(self) -> Self:
27
- return self
28
-
29
-
30
- def is_marker(obj: Any) -> bool:
31
- """Checks if an object is a marker."""
32
- return isinstance(obj, Marker)
33
-
34
-
35
- class Event:
36
- """Represents an event object."""
37
-
38
- __slots__ = ()
39
-
40
-
41
- def is_event_type(obj: Any) -> bool:
42
- """Checks if an object is an event type."""
43
- return inspect.isclass(obj) and issubclass(obj, Event)
44
-
45
-
46
- class InstanceProxy(wrapt.ObjectProxy): # type: ignore
47
- def __init__(self, wrapped: Any, *, interface: type[Any]) -> None:
48
- super().__init__(wrapped) # type: ignore
49
- self._self_interface = interface
50
-
51
- @property
52
- def interface(self) -> type[Any]:
53
- return self._self_interface
54
-
55
- def __getattribute__(self, item: str) -> Any:
56
- if item in "interface":
57
- return object.__getattribute__(self, item)
58
- return object.__getattribute__(self, item)
59
-
60
-
61
- class ProviderKind(enum.IntEnum):
62
- CLASS = 1
63
- FUNCTION = 2
64
- COROUTINE = 3
65
- GENERATOR = 4
66
- ASYNC_GENERATOR = 5
67
-
68
- @classmethod
69
- def from_call(cls, call: Callable[..., Any]) -> ProviderKind:
70
- if inspect.isclass(call):
71
- return cls.CLASS
72
- elif inspect.iscoroutinefunction(call):
73
- return cls.COROUTINE
74
- elif inspect.isasyncgenfunction(call):
75
- return cls.ASYNC_GENERATOR
76
- elif inspect.isgeneratorfunction(call):
77
- return cls.GENERATOR
78
- elif inspect.isfunction(call) or inspect.ismethod(call):
79
- return cls.FUNCTION
80
- raise TypeError(
81
- f"The provider `{call}` is invalid because it is not a callable "
82
- "object. Only callable providers are allowed."
83
- )
84
-
85
- @classmethod
86
- def is_resource(cls, kind: ProviderKind) -> bool:
87
- return kind in (cls.GENERATOR, cls.ASYNC_GENERATOR)
88
-
89
-
90
- @dataclass(kw_only=True, frozen=True)
91
- class Provider:
92
- call: Callable[..., Any]
93
- scope: Scope
94
- interface: Any
95
- name: str
96
- parameters: list[inspect.Parameter]
97
- kind: ProviderKind
98
-
99
- def __str__(self) -> str:
100
- return self.name
101
-
102
- @cached_property
103
- def is_class(self) -> bool:
104
- return self.kind == ProviderKind.CLASS
105
-
106
- @cached_property
107
- def is_coroutine(self) -> bool:
108
- return self.kind == ProviderKind.COROUTINE
109
-
110
- @cached_property
111
- def is_generator(self) -> bool:
112
- return self.kind == ProviderKind.GENERATOR
113
-
114
- @cached_property
115
- def is_async_generator(self) -> bool:
116
- return self.kind == ProviderKind.ASYNC_GENERATOR
117
-
118
- @cached_property
119
- def is_async(self) -> bool:
120
- return self.is_coroutine or self.is_async_generator
121
-
122
- @cached_property
123
- def is_resource(self) -> bool:
124
- return ProviderKind.is_resource(self.kind)
125
-
126
-
127
- class ProviderArgs(NamedTuple):
128
- call: Callable[..., Any]
129
- scope: Scope
130
- interface: Any = NOT_SET
131
-
132
-
133
- class ProviderDecoratorArgs(NamedTuple):
134
- scope: Scope
135
- override: bool
136
-
137
-
138
- class ScannedDependency(NamedTuple):
139
- member: Any
140
- module: ModuleType
141
-
142
-
143
- class InjectableDecoratorArgs(NamedTuple):
144
- wrapped: bool
145
- tags: Iterable[str] | None
@@ -1,28 +0,0 @@
1
- anydi/__init__.py,sha256=aAq10a1V_zQ3_Me3p_pll5d1O77PIgqotkOm3pshORI,495
2
- anydi/_container.py,sha256=b8Ul94gIP1-F0f_FJoWM9kRyyB8Do7YEmFvpynv8Qu0,41783
3
- anydi/_context.py,sha256=pR97uMzafpuDe2jZcqnCShXmWp5bijzmNUOm6A6-NtY,3022
4
- anydi/_types.py,sha256=MwLBNhjqndCraEUCIfEpvgZEJOaVmKan3zAh8BbFa0Y,3657
5
- anydi/_utils.py,sha256=JHTp46uWdsWYJv4U1iU_twEnX__L3CEodK4yzhq3bHg,4858
6
- anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- anydi/ext/_utils.py,sha256=U6sRqWzccWUu7eMhbXX1NrwcaxitQF9cO1KxnKF37gw,2566
9
- anydi/ext/fastapi.py,sha256=AEL3ubu-LxUPHMMt1YIn3En_JZC7nyBKmKxmhka3O3c,2436
10
- anydi/ext/faststream.py,sha256=TBpN8DwZ4qOMVo_u1AgG5tAKcTMwJnV8eWavPMgb6lg,1923
11
- anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
12
- anydi/ext/pytest_plugin.py,sha256=yR_vos8qt8uFS9uy_G_HJjNgudzJIXawrtpWnn3Pu_s,4613
13
- anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
14
- anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
15
- anydi/ext/django/_settings.py,sha256=Z0RlAuXoO73oahWeMkK10w8c-4uCBde-DBpeKTV5USY,853
16
- anydi/ext/django/_utils.py,sha256=q6X6GApBm0oBK8DnoRZhTq2m4tAdKRYL__gVgKn3idg,3977
17
- anydi/ext/django/apps.py,sha256=mjbf_mDCpNSriGnILzhRIr8wFHLMEK8sUerbmRku6i0,2844
18
- anydi/ext/django/middleware.py,sha256=5OUdp0OWRozyW338Sq04BDhacaFlyUTTOduS_7EwCTA,854
19
- anydi/ext/django/ninja/__init__.py,sha256=kW3grUgWp_nkWSG_-39ADHMrZLGNcj9TsJ9OW8iWWrk,546
20
- anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI8W-oO2w,2696
21
- anydi/ext/django/ninja/_signature.py,sha256=uYrG2PFgG2IlXrM24rgDOtRBnrbKQeAMl6ErypRi8qs,2260
22
- anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- anydi/ext/starlette/middleware.py,sha256=9CQtGg5ZzUz2gFSzJr8U4BWzwNjK8XMctm3n52M77Z0,792
24
- anydi-0.40.0.dist-info/METADATA,sha256=zevT18jpCBf7yCCoMgTvkiD-_Pl2_p_A3TdYM4ROIns,4957
25
- anydi-0.40.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- anydi-0.40.0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
27
- anydi-0.40.0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
- anydi-0.40.0.dist-info/RECORD,,
File without changes