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/_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 TYPE_CHECKING, Any, Dict, List, Tuple, Type, TypeVar, cast
5
+ from typing import Any
7
6
 
8
- from typing_extensions import Self, final
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
- T = TypeVar("T")
12
+ class InstanceContext:
13
+ """A context to store instances."""
17
14
 
15
+ __slots__ = ("_instances", "_stack", "_async_stack")
18
16
 
19
- class ScopedContext(abc.ABC):
20
- """ScopedContext base class."""
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: Interface[T], provider: Provider) -> T:
140
- """Get an instance of a dependency from the scoped context.
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
- Args:
168
- interface: The interface of the dependency.
169
- provider: The provider for the instance.
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
- Returns:
172
- An async instance of the dependency.
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 _acreate_resource(self, provider: Provider) -> Any:
210
- """Create a resource asynchronously using the provider.
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
- Args:
213
- provider: The provider for the resource.
38
+ def __setitem__(self, interface: type[Any], value: Any) -> None:
39
+ self._instances[interface] = value
214
40
 
215
- Returns:
216
- The created resource.
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 delete(self, interface: AnyInterface) -> None:
223
- """Delete a dependency instance from the scoped context.
44
+ def __contains__(self, interface: type[Any]) -> bool:
45
+ return interface in self._instances
224
46
 
225
- Args:
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: Type[BaseException] | None,
56
+ exc_type: type[BaseException] | None,
241
57
  exc_val: BaseException | None,
242
58
  exc_tb: TracebackType | None,
243
- ) -> None:
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.close()
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: Type[BaseException] | None,
73
+ exc_type: type[BaseException] | None,
269
74
  exc_val: BaseException | None,
270
75
  exc_tb: TracebackType | None,
271
- ) -> None:
272
- """Exit the context asynchronously.
273
-
274
- Args:
275
- exc_type: The type of the exception, if any.
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 run_async(self._stack.close)
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