anydi 0.22.1__py3-none-any.whl → 0.37.4__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/__init__.py +14 -14
- anydi/_container.py +811 -571
- anydi/_context.py +39 -281
- anydi/_provider.py +232 -0
- anydi/_types.py +49 -96
- anydi/_utils.py +108 -77
- anydi/ext/_utils.py +49 -28
- anydi/ext/django/__init__.py +9 -0
- anydi/ext/django/_container.py +18 -0
- anydi/ext/django/_settings.py +39 -0
- anydi/ext/django/_utils.py +128 -0
- anydi/ext/django/apps.py +85 -0
- anydi/ext/django/middleware.py +28 -0
- anydi/ext/django/ninja/__init__.py +16 -0
- anydi/ext/django/ninja/_operation.py +75 -0
- anydi/ext/django/ninja/_signature.py +64 -0
- anydi/ext/fastapi.py +11 -27
- anydi/ext/faststream.py +58 -0
- anydi/ext/pydantic_settings.py +48 -0
- anydi/ext/pytest_plugin.py +67 -41
- anydi/ext/starlette/middleware.py +2 -16
- {anydi-0.22.1.dist-info → anydi-0.37.4.dist-info}/METADATA +71 -21
- anydi-0.37.4.dist-info/RECORD +29 -0
- {anydi-0.22.1.dist-info → anydi-0.37.4.dist-info}/WHEEL +1 -1
- anydi-0.37.4.dist-info/entry_points.txt +2 -0
- anydi/_logger.py +0 -3
- anydi/_module.py +0 -124
- anydi/_scanner.py +0 -233
- anydi-0.22.1.dist-info/RECORD +0 -20
- anydi-0.22.1.dist-info/entry_points.txt +0 -3
- {anydi-0.22.1.dist-info → anydi-0.37.4.dist-info/licenses}/LICENSE +0 -0
anydi/_context.py
CHANGED
|
@@ -1,326 +1,84 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import abc
|
|
4
3
|
import contextlib
|
|
5
4
|
from types import TracebackType
|
|
6
|
-
from typing import
|
|
5
|
+
from typing import Any
|
|
7
6
|
|
|
8
|
-
from typing_extensions import Self
|
|
7
|
+
from typing_extensions import Self
|
|
9
8
|
|
|
10
|
-
from ._types import AnyInterface, Interface, Provider
|
|
11
9
|
from ._utils import run_async
|
|
12
10
|
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from ._container import Container
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
class InstanceContext:
|
|
13
|
+
"""A context to store instances."""
|
|
17
14
|
|
|
15
|
+
__slots__ = ("_instances", "_stack", "_async_stack")
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def __init__(self, container: Container) -> None:
|
|
23
|
-
self.container = container
|
|
24
|
-
|
|
25
|
-
@abc.abstractmethod
|
|
26
|
-
def get(self, interface: Interface[T], provider: Provider) -> T:
|
|
27
|
-
"""Get an instance of a dependency from the scoped context.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
interface: The interface of the dependency.
|
|
31
|
-
provider: The provider for the instance.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
An instance of the dependency.
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
@abc.abstractmethod
|
|
38
|
-
async def aget(self, interface: Interface[T], provider: Provider) -> T:
|
|
39
|
-
"""Get an async instance of a dependency from the scoped context.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
interface: The interface of the dependency.
|
|
43
|
-
provider: The provider for the instance.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
An async instance of the dependency.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
def _create_instance(self, provider: Provider) -> Any:
|
|
50
|
-
"""Create an instance using the provider.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
provider: The provider for the instance.
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
The created instance.
|
|
57
|
-
|
|
58
|
-
Raises:
|
|
59
|
-
TypeError: If the provider's instance is a coroutine provider
|
|
60
|
-
and synchronous mode is used.
|
|
61
|
-
"""
|
|
62
|
-
if provider.is_coroutine:
|
|
63
|
-
raise TypeError(
|
|
64
|
-
f"The instance for the coroutine provider `{provider}` cannot be "
|
|
65
|
-
"created in synchronous mode."
|
|
66
|
-
)
|
|
67
|
-
args, kwargs = self._get_provider_arguments(provider)
|
|
68
|
-
return provider.obj(*args, **kwargs)
|
|
69
|
-
|
|
70
|
-
async def _acreate_instance(self, provider: Provider) -> Any:
|
|
71
|
-
"""Create an instance asynchronously using the provider.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
provider: The provider for the instance.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
The created instance.
|
|
78
|
-
|
|
79
|
-
Raises:
|
|
80
|
-
TypeError: If the provider's instance is a coroutine provider
|
|
81
|
-
and asynchronous mode is used.
|
|
82
|
-
"""
|
|
83
|
-
args, kwargs = await self._aget_provider_arguments(provider)
|
|
84
|
-
if provider.is_coroutine:
|
|
85
|
-
return await provider.obj(*args, **kwargs)
|
|
86
|
-
return await run_async(provider.obj, *args, **kwargs)
|
|
87
|
-
|
|
88
|
-
def _get_provider_arguments(
|
|
89
|
-
self, provider: Provider
|
|
90
|
-
) -> Tuple[List[Any], Dict[str, Any]]:
|
|
91
|
-
"""Retrieve the arguments for a provider.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
provider: The provider object.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
The arguments for the provider.
|
|
98
|
-
"""
|
|
99
|
-
args, kwargs = [], {}
|
|
100
|
-
for parameter in provider.parameters.values():
|
|
101
|
-
instance = self.container.resolve(parameter.annotation)
|
|
102
|
-
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
103
|
-
args.append(instance)
|
|
104
|
-
else:
|
|
105
|
-
kwargs[parameter.name] = instance
|
|
106
|
-
return args, kwargs
|
|
107
|
-
|
|
108
|
-
async def _aget_provider_arguments(
|
|
109
|
-
self, provider: Provider
|
|
110
|
-
) -> Tuple[List[Any], Dict[str, Any]]:
|
|
111
|
-
"""Asynchronously retrieve the arguments for a provider.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
provider: The provider object.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
The arguments for the provider.
|
|
118
|
-
"""
|
|
119
|
-
args, kwargs = [], {}
|
|
120
|
-
for parameter in provider.parameters.values():
|
|
121
|
-
instance = await self.container.aresolve(parameter.annotation)
|
|
122
|
-
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
123
|
-
args.append(instance)
|
|
124
|
-
else:
|
|
125
|
-
kwargs[parameter.name] = instance
|
|
126
|
-
return args, kwargs
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class ResourceScopedContext(ScopedContext):
|
|
130
|
-
"""ScopedContext with closable resources support."""
|
|
131
|
-
|
|
132
|
-
def __init__(self, container: Container) -> None:
|
|
133
|
-
"""Initialize the ScopedContext."""
|
|
134
|
-
super().__init__(container)
|
|
135
|
-
self._instances: Dict[Type[Any], Any] = {}
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self._instances: dict[type[Any], Any] = {}
|
|
136
19
|
self._stack = contextlib.ExitStack()
|
|
137
20
|
self._async_stack = contextlib.AsyncExitStack()
|
|
138
21
|
|
|
139
|
-
def get(self, interface:
|
|
140
|
-
"""Get an instance
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
interface: The interface of the dependency.
|
|
144
|
-
provider: The provider for the instance.
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
An instance of the dependency.
|
|
148
|
-
"""
|
|
149
|
-
instance = self._instances.get(interface)
|
|
150
|
-
if instance is None:
|
|
151
|
-
if provider.is_generator:
|
|
152
|
-
instance = self._create_resource(provider)
|
|
153
|
-
elif provider.is_async_generator:
|
|
154
|
-
raise TypeError(
|
|
155
|
-
f"The provider `{provider}` cannot be started in synchronous mode "
|
|
156
|
-
"because it is an asynchronous provider. Please start the provider "
|
|
157
|
-
"in asynchronous mode before using it."
|
|
158
|
-
)
|
|
159
|
-
else:
|
|
160
|
-
instance = self._create_instance(provider)
|
|
161
|
-
self._instances[interface] = instance
|
|
162
|
-
return cast(T, instance)
|
|
163
|
-
|
|
164
|
-
async def aget(self, interface: Interface[T], provider: Provider) -> T:
|
|
165
|
-
"""Get an async instance of a dependency from the scoped context.
|
|
22
|
+
def get(self, interface: type[Any]) -> Any | None:
|
|
23
|
+
"""Get an instance from the context."""
|
|
24
|
+
return self._instances.get(interface)
|
|
166
25
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
26
|
+
def set(self, interface: type[Any], value: Any) -> None:
|
|
27
|
+
"""Set an instance in the context."""
|
|
28
|
+
self._instances[interface] = value
|
|
170
29
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"""
|
|
174
|
-
instance = self._instances.get(interface)
|
|
175
|
-
if instance is None:
|
|
176
|
-
if provider.is_generator:
|
|
177
|
-
instance = await run_async(self._create_resource, provider)
|
|
178
|
-
elif provider.is_async_generator:
|
|
179
|
-
instance = await self._acreate_resource(provider)
|
|
180
|
-
else:
|
|
181
|
-
instance = await self._acreate_instance(provider)
|
|
182
|
-
self._instances[interface] = instance
|
|
183
|
-
return cast(T, instance)
|
|
184
|
-
|
|
185
|
-
def has(self, interface: AnyInterface) -> bool:
|
|
186
|
-
"""Check if the scoped context has an instance of the dependency.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
interface: The interface of the dependency.
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
Whether the scoped context has an instance of the dependency.
|
|
193
|
-
"""
|
|
194
|
-
return interface in self._instances
|
|
195
|
-
|
|
196
|
-
def _create_resource(self, provider: Provider) -> Any:
|
|
197
|
-
"""Create a resource using the provider.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
provider: The provider for the resource.
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
The created resource.
|
|
204
|
-
"""
|
|
205
|
-
args, kwargs = self._get_provider_arguments(provider)
|
|
206
|
-
cm = contextlib.contextmanager(provider.obj)(*args, **kwargs)
|
|
30
|
+
def enter(self, cm: contextlib.AbstractContextManager[Any]) -> Any:
|
|
31
|
+
"""Enter the context."""
|
|
207
32
|
return self._stack.enter_context(cm)
|
|
208
33
|
|
|
209
|
-
async def
|
|
210
|
-
"""
|
|
34
|
+
async def aenter(self, cm: contextlib.AbstractAsyncContextManager[Any]) -> Any:
|
|
35
|
+
"""Enter the context asynchronously."""
|
|
36
|
+
return await self._async_stack.enter_async_context(cm)
|
|
211
37
|
|
|
212
|
-
|
|
213
|
-
|
|
38
|
+
def __setitem__(self, interface: type[Any], value: Any) -> None:
|
|
39
|
+
self._instances[interface] = value
|
|
214
40
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"""
|
|
218
|
-
args, kwargs = await self._aget_provider_arguments(provider)
|
|
219
|
-
cm = contextlib.asynccontextmanager(provider.obj)(*args, **kwargs)
|
|
220
|
-
return await self._async_stack.enter_async_context(cm)
|
|
41
|
+
def __getitem__(self, interface: type[Any]) -> Any:
|
|
42
|
+
return self._instances[interface]
|
|
221
43
|
|
|
222
|
-
def
|
|
223
|
-
|
|
44
|
+
def __contains__(self, interface: type[Any]) -> bool:
|
|
45
|
+
return interface in self._instances
|
|
224
46
|
|
|
225
|
-
|
|
226
|
-
interface: The interface of the dependency.
|
|
227
|
-
"""
|
|
47
|
+
def __delitem__(self, interface: type[Any]) -> None:
|
|
228
48
|
self._instances.pop(interface, None)
|
|
229
49
|
|
|
230
50
|
def __enter__(self) -> Self:
|
|
231
|
-
"""Enter the context.
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
The scoped context.
|
|
235
|
-
"""
|
|
51
|
+
"""Enter the context."""
|
|
236
52
|
return self
|
|
237
53
|
|
|
238
54
|
def __exit__(
|
|
239
55
|
self,
|
|
240
|
-
exc_type:
|
|
56
|
+
exc_type: type[BaseException] | None,
|
|
241
57
|
exc_val: BaseException | None,
|
|
242
58
|
exc_tb: TracebackType | None,
|
|
243
|
-
) ->
|
|
244
|
-
"""Exit the context.
|
|
245
|
-
|
|
246
|
-
Args:
|
|
247
|
-
exc_type: The type of the exception, if any.
|
|
248
|
-
exc_val: The exception instance, if any.
|
|
249
|
-
exc_tb: The traceback, if any.
|
|
250
|
-
"""
|
|
251
|
-
self.close()
|
|
252
|
-
return
|
|
59
|
+
) -> Any:
|
|
60
|
+
"""Exit the context."""
|
|
61
|
+
return self._stack.__exit__(exc_type, exc_val, exc_tb)
|
|
253
62
|
|
|
254
63
|
def close(self) -> None:
|
|
255
64
|
"""Close the scoped context."""
|
|
256
|
-
self._stack.
|
|
65
|
+
self._stack.__exit__(None, None, None)
|
|
257
66
|
|
|
258
67
|
async def __aenter__(self) -> Self:
|
|
259
|
-
"""Enter the context asynchronously.
|
|
260
|
-
|
|
261
|
-
Returns:
|
|
262
|
-
The scoped context.
|
|
263
|
-
"""
|
|
68
|
+
"""Enter the context asynchronously."""
|
|
264
69
|
return self
|
|
265
70
|
|
|
266
71
|
async def __aexit__(
|
|
267
72
|
self,
|
|
268
|
-
exc_type:
|
|
73
|
+
exc_type: type[BaseException] | None,
|
|
269
74
|
exc_val: BaseException | None,
|
|
270
75
|
exc_tb: TracebackType | None,
|
|
271
|
-
) ->
|
|
272
|
-
"""Exit the context asynchronously.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
exc_val: The exception instance, if any.
|
|
277
|
-
exc_tb: The traceback, if any.
|
|
278
|
-
"""
|
|
279
|
-
await self.aclose()
|
|
280
|
-
return
|
|
76
|
+
) -> bool:
|
|
77
|
+
"""Exit the context asynchronously."""
|
|
78
|
+
return await run_async(
|
|
79
|
+
self.__exit__, exc_type, exc_val, exc_tb
|
|
80
|
+
) or await self._async_stack.__aexit__(exc_type, exc_val, exc_tb)
|
|
281
81
|
|
|
282
82
|
async def aclose(self) -> None:
|
|
283
83
|
"""Close the scoped context asynchronously."""
|
|
284
|
-
await
|
|
285
|
-
await self._async_stack.aclose()
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@final
|
|
289
|
-
class SingletonContext(ResourceScopedContext):
|
|
290
|
-
"""A scoped context representing the "singleton" scope."""
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
@final
|
|
294
|
-
class RequestContext(ResourceScopedContext):
|
|
295
|
-
"""A scoped context representing the "request" scope."""
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
@final
|
|
299
|
-
class TransientContext(ScopedContext):
|
|
300
|
-
"""A scoped context representing the "transient" scope."""
|
|
301
|
-
|
|
302
|
-
def get(self, interface: Interface[T], provider: Provider) -> T:
|
|
303
|
-
"""Get an instance of a dependency from the transient context.
|
|
304
|
-
|
|
305
|
-
Args:
|
|
306
|
-
interface: The interface of the dependency.
|
|
307
|
-
provider: The provider for the instance.
|
|
308
|
-
|
|
309
|
-
Returns:
|
|
310
|
-
An instance of the dependency.
|
|
311
|
-
"""
|
|
312
|
-
instance = self._create_instance(provider)
|
|
313
|
-
return cast(T, instance)
|
|
314
|
-
|
|
315
|
-
async def aget(self, interface: Interface[T], provider: Provider) -> T:
|
|
316
|
-
"""Get an async instance of a dependency from the transient context.
|
|
317
|
-
|
|
318
|
-
Args:
|
|
319
|
-
interface: The interface of the dependency.
|
|
320
|
-
provider: The provider for the instance.
|
|
321
|
-
|
|
322
|
-
Returns:
|
|
323
|
-
An instance of the dependency.
|
|
324
|
-
"""
|
|
325
|
-
instance = await self._acreate_instance(provider)
|
|
326
|
-
return cast(T, instance)
|
|
84
|
+
await self.__aexit__(None, None, None)
|
anydi/_provider.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import uuid
|
|
5
|
+
from collections.abc import AsyncIterator, Iterator
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from typing_extensions import get_args, get_origin
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from types import NoneType
|
|
13
|
+
except ImportError:
|
|
14
|
+
NoneType = type(None) # type: ignore[misc]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from ._types import Event, Scope
|
|
18
|
+
from ._utils import get_full_qualname, get_typed_annotation
|
|
19
|
+
|
|
20
|
+
_sentinel = object()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CallableKind(IntEnum):
|
|
24
|
+
CLASS = 1
|
|
25
|
+
FUNCTION = 2
|
|
26
|
+
COROUTINE = 3
|
|
27
|
+
GENERATOR = 4
|
|
28
|
+
ASYNC_GENERATOR = 5
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Provider:
|
|
32
|
+
__slots__ = (
|
|
33
|
+
"_call",
|
|
34
|
+
"_call_module",
|
|
35
|
+
"_call_globals",
|
|
36
|
+
"_scope",
|
|
37
|
+
"_qualname",
|
|
38
|
+
"_kind",
|
|
39
|
+
"_interface",
|
|
40
|
+
"_parameters",
|
|
41
|
+
"_is_class",
|
|
42
|
+
"_is_coroutine",
|
|
43
|
+
"_is_generator",
|
|
44
|
+
"_is_async_generator",
|
|
45
|
+
"_is_async",
|
|
46
|
+
"_is_resource",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, call: Callable[..., Any], *, scope: Scope, interface: Any = _sentinel
|
|
51
|
+
) -> None:
|
|
52
|
+
self._call = call
|
|
53
|
+
self._call_module = getattr(call, "__module__", None)
|
|
54
|
+
self._call_globals = getattr(call, "__globals__", {})
|
|
55
|
+
self._scope = scope
|
|
56
|
+
self._qualname = get_full_qualname(call)
|
|
57
|
+
|
|
58
|
+
# Detect the kind of callable provider
|
|
59
|
+
self._detect_kind()
|
|
60
|
+
|
|
61
|
+
self._is_class = self._kind == CallableKind.CLASS
|
|
62
|
+
self._is_coroutine = self._kind == CallableKind.COROUTINE
|
|
63
|
+
self._is_generator = self._kind == CallableKind.GENERATOR
|
|
64
|
+
self._is_async_generator = self._kind == CallableKind.ASYNC_GENERATOR
|
|
65
|
+
self._is_async = self._is_coroutine or self._is_async_generator
|
|
66
|
+
self._is_resource = self._is_generator or self._is_async_generator
|
|
67
|
+
|
|
68
|
+
# Validate the scope of the provider
|
|
69
|
+
self._validate_scope()
|
|
70
|
+
|
|
71
|
+
# Get the signature
|
|
72
|
+
signature = inspect.signature(call)
|
|
73
|
+
|
|
74
|
+
# Detect the interface
|
|
75
|
+
self._detect_interface(interface, signature)
|
|
76
|
+
|
|
77
|
+
# Detect the parameters
|
|
78
|
+
self._detect_parameters(signature)
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
return self._qualname
|
|
82
|
+
|
|
83
|
+
def __eq__(self, other: object) -> bool:
|
|
84
|
+
if not isinstance(other, Provider):
|
|
85
|
+
return NotImplemented # pragma: no cover
|
|
86
|
+
return (
|
|
87
|
+
self._call == other._call
|
|
88
|
+
and self._scope == other._scope
|
|
89
|
+
and self._interface == other._interface
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def call(self) -> Callable[..., Any]:
|
|
94
|
+
return self._call
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def kind(self) -> CallableKind:
|
|
98
|
+
return self._kind
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def scope(self) -> Scope:
|
|
102
|
+
return self._scope
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def interface(self) -> Any:
|
|
106
|
+
return self._interface
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def parameters(self) -> list[inspect.Parameter]:
|
|
110
|
+
return self._parameters
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def is_class(self) -> bool:
|
|
114
|
+
"""Check if the provider is a class."""
|
|
115
|
+
return self._is_class
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def is_coroutine(self) -> bool:
|
|
119
|
+
"""Check if the provider is a coroutine."""
|
|
120
|
+
return self._is_coroutine
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def is_generator(self) -> bool:
|
|
124
|
+
"""Check if the provider is a generator."""
|
|
125
|
+
return self._is_generator
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_async_generator(self) -> bool:
|
|
129
|
+
"""Check if the provider is an async generator."""
|
|
130
|
+
return self._is_async_generator
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def is_async(self) -> bool:
|
|
134
|
+
"""Check if the provider is an async callable."""
|
|
135
|
+
return self._is_async
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def is_resource(self) -> bool:
|
|
139
|
+
"""Check if the provider is a resource."""
|
|
140
|
+
return self._is_resource
|
|
141
|
+
|
|
142
|
+
def _validate_scope(self) -> None:
|
|
143
|
+
"""Validate the scope of the provider."""
|
|
144
|
+
if self.scope not in get_args(Scope):
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"The scope provided is invalid. Only the following scopes are "
|
|
147
|
+
f"supported: {', '.join(get_args(Scope))}. Please use one of the "
|
|
148
|
+
"supported scopes when registering a provider."
|
|
149
|
+
)
|
|
150
|
+
if self.is_resource and self.scope == "transient":
|
|
151
|
+
raise TypeError(
|
|
152
|
+
f"The resource provider `{self}` is attempting to register "
|
|
153
|
+
"with a transient scope, which is not allowed."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _detect_kind(self) -> None:
|
|
157
|
+
"""Detect the kind of callable provider."""
|
|
158
|
+
if inspect.isclass(self.call):
|
|
159
|
+
self._kind = CallableKind.CLASS
|
|
160
|
+
elif inspect.iscoroutinefunction(self.call):
|
|
161
|
+
self._kind = CallableKind.COROUTINE
|
|
162
|
+
elif inspect.isasyncgenfunction(self.call):
|
|
163
|
+
self._kind = CallableKind.ASYNC_GENERATOR
|
|
164
|
+
elif inspect.isgeneratorfunction(self.call):
|
|
165
|
+
self._kind = CallableKind.GENERATOR
|
|
166
|
+
elif inspect.isfunction(self.call) or inspect.ismethod(self.call):
|
|
167
|
+
self._kind = CallableKind.FUNCTION
|
|
168
|
+
else:
|
|
169
|
+
raise TypeError(
|
|
170
|
+
f"The provider `{self.call}` is invalid because it is not a callable "
|
|
171
|
+
"object. Only callable providers are allowed."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _detect_interface(self, interface: Any, signature: inspect.Signature) -> None:
|
|
175
|
+
"""Detect the interface of callable provider."""
|
|
176
|
+
# If the callable is a class, return the class itself
|
|
177
|
+
if self._kind == CallableKind.CLASS:
|
|
178
|
+
self._interface = self._call
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
if interface is _sentinel:
|
|
182
|
+
interface = self._resolve_interface(interface, signature)
|
|
183
|
+
|
|
184
|
+
# If the callable is an iterator, return the actual type
|
|
185
|
+
iterator_types = {Iterator, AsyncIterator}
|
|
186
|
+
if interface in iterator_types or get_origin(interface) in iterator_types:
|
|
187
|
+
if args := get_args(interface):
|
|
188
|
+
interface = args[0]
|
|
189
|
+
# If the callable is a generator, return the resource type
|
|
190
|
+
if interface is NoneType or interface is None:
|
|
191
|
+
self._interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
|
|
192
|
+
return
|
|
193
|
+
else:
|
|
194
|
+
raise TypeError(
|
|
195
|
+
f"Cannot use `{self}` resource type annotation "
|
|
196
|
+
"without actual type argument."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# None interface is not allowed
|
|
200
|
+
if interface in {None, NoneType}:
|
|
201
|
+
raise TypeError(f"Missing `{self}` provider return annotation.")
|
|
202
|
+
|
|
203
|
+
# Set the interface
|
|
204
|
+
self._interface = interface
|
|
205
|
+
|
|
206
|
+
def _resolve_interface(self, interface: Any, signature: inspect.Signature) -> Any:
|
|
207
|
+
"""Resolve the interface of the callable provider."""
|
|
208
|
+
interface = signature.return_annotation
|
|
209
|
+
if interface is inspect.Signature.empty:
|
|
210
|
+
return None
|
|
211
|
+
return get_typed_annotation(
|
|
212
|
+
interface,
|
|
213
|
+
self._call_globals,
|
|
214
|
+
module=self._call_module,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _detect_parameters(self, signature: inspect.Signature) -> None:
|
|
218
|
+
"""Detect the parameters of the callable provider."""
|
|
219
|
+
parameters = []
|
|
220
|
+
for parameter in signature.parameters.values():
|
|
221
|
+
if parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
|
|
222
|
+
raise TypeError(
|
|
223
|
+
f"Positional-only parameter `{parameter.name}` is not allowed "
|
|
224
|
+
f"in the provider `{self}`."
|
|
225
|
+
)
|
|
226
|
+
annotation = get_typed_annotation(
|
|
227
|
+
parameter.annotation,
|
|
228
|
+
self._call_globals,
|
|
229
|
+
module=self._call_module,
|
|
230
|
+
)
|
|
231
|
+
parameters.append(parameter.replace(annotation=annotation))
|
|
232
|
+
self._parameters = parameters
|