anydi 0.38.1__py3-none-any.whl → 0.38.2rc1__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/_types.py CHANGED
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import enum
3
4
  import inspect
4
5
  from collections.abc import Iterable
6
+ from dataclasses import dataclass
7
+ from functools import cached_property
5
8
  from types import ModuleType
6
- from typing import Annotated, Any, NamedTuple, Union
9
+ from typing import Annotated, Any, Callable, NamedTuple, Union
7
10
 
8
11
  import wrapt
9
12
  from typing_extensions import Literal, Self, TypeAlias
@@ -12,6 +15,8 @@ Scope = Literal["transient", "singleton", "request"]
12
15
 
13
16
  AnyInterface: TypeAlias = Union[type[Any], Annotated[Any, ...]]
14
17
 
18
+ NOT_SET = object()
19
+
15
20
 
16
21
  class Marker:
17
22
  """A marker class for marking dependencies."""
@@ -53,12 +58,85 @@ class InstanceProxy(wrapt.ObjectProxy): # type: ignore[misc]
53
58
  return object.__getattribute__(self, item)
54
59
 
55
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
+
86
+ @dataclass(kw_only=True, frozen=True)
87
+ class ProviderParameter:
88
+ pass
89
+
90
+
91
+ @dataclass(kw_only=True, frozen=True)
92
+ class Provider:
93
+ call: Callable[..., Any]
94
+ scope: Scope
95
+ interface: Any
96
+ name: str
97
+ parameters: list[inspect.Parameter]
98
+ kind: ProviderKind
99
+
100
+ def __str__(self) -> str:
101
+ return self.name
102
+
103
+ @cached_property
104
+ def is_class(self) -> bool:
105
+ return self.kind == ProviderKind.CLASS
106
+
107
+ @cached_property
108
+ def is_coroutine(self) -> bool:
109
+ return self.kind == ProviderKind.COROUTINE
110
+
111
+ @cached_property
112
+ def is_generator(self) -> bool:
113
+ return self.kind == ProviderKind.GENERATOR
114
+
115
+ @cached_property
116
+ def is_async_generator(self) -> bool:
117
+ return self.kind == ProviderKind.ASYNC_GENERATOR
118
+
119
+ @cached_property
120
+ def is_async(self) -> bool:
121
+ return self.is_coroutine or self.is_async_generator
122
+
123
+ @cached_property
124
+ def is_resource(self) -> bool:
125
+ return self.is_generator or self.is_async_generator
126
+
127
+
128
+ class ProviderArgs(NamedTuple):
129
+ call: Callable[..., Any]
130
+ scope: Scope
131
+ interface: Any = NOT_SET
132
+
133
+
56
134
  class ProviderDecoratorArgs(NamedTuple):
57
135
  scope: Scope
58
136
  override: bool
59
137
 
60
138
 
61
- class Dependency(NamedTuple):
139
+ class ScannedDependency(NamedTuple):
62
140
  member: Any
63
141
  module: ModuleType
64
142
 
anydi/_utils.py CHANGED
@@ -20,6 +20,9 @@ P = ParamSpec("P")
20
20
 
21
21
  def get_full_qualname(obj: Any) -> str:
22
22
  """Get the fully qualified name of an object."""
23
+ if isinstance(obj, str):
24
+ return obj
25
+
23
26
  # Get module and qualname with defaults to handle non-types directly
24
27
  module = getattr(obj, "__module__", type(obj).__module__)
25
28
  qualname = getattr(obj, "__qualname__", type(obj).__qualname__)
@@ -35,11 +35,9 @@ CONTAINER_FIXTURE_NAME = "container"
35
35
 
36
36
 
37
37
  @pytest.fixture
38
- def anydi_setup_container(
39
- request: pytest.FixtureRequest,
40
- ) -> Iterator[Container]:
38
+ def anydi_setup_container(request: pytest.FixtureRequest) -> Container:
41
39
  try:
42
- container = request.getfixturevalue(CONTAINER_FIXTURE_NAME)
40
+ return cast(Container, request.getfixturevalue(CONTAINER_FIXTURE_NAME))
43
41
  except pytest.FixtureLookupError as exc:
44
42
  exc.msg = (
45
43
  "`container` fixture is not found. Make sure to define it in your test "
@@ -47,8 +45,6 @@ def anydi_setup_container(
47
45
  )
48
46
  raise exc
49
47
 
50
- yield container
51
-
52
48
 
53
49
  @pytest.fixture
54
50
  def _anydi_should_inject(request: pytest.FixtureRequest) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.38.1
3
+ Version: 0.38.2rc1
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>
@@ -1,16 +1,15 @@
1
- anydi/__init__.py,sha256=OfRg2EfXD65pHTGQKhfkABMwUhw5LvsuTQV_Tv4V4wk,501
2
- anydi/_container.py,sha256=b2WKdDLwLj50u0DUOdAku2oYOKpw3UD7rCuytu2x_R4,38732
1
+ anydi/__init__.py,sha256=aAq10a1V_zQ3_Me3p_pll5d1O77PIgqotkOm3pshORI,495
2
+ anydi/_container.py,sha256=1reHespAhCYVs8_zOHJWwhoLoSrQhs9X5c51uRVm-Mw,42424
3
3
  anydi/_context.py,sha256=7LV_SL4QWkJeiG7_4D9PZ5lmU-MPzhofxC95zCgY9Gc,2651
4
- anydi/_provider.py,sha256=1IyxHO83NHjsPDHLDIZtW1pJ7i8VpWD3EM4T6duw9zA,7661
5
- anydi/_types.py,sha256=fdO4xNXtGMxVArmlfDkFYbyR895ixkBTW6V8lMceN7Q,1562
6
- anydi/_utils.py,sha256=INI0jNIXrJ6LS4zqJymMO2yUEobpxmBGASf4G_vR6AU,4378
4
+ anydi/_types.py,sha256=S7scIdAzc2SyDUfEA4Knc89W6q-sdoGa3UBq-dqtq2c,3582
5
+ anydi/_utils.py,sha256=OxePzLBD_mn3Ex2I93udoodlIARyKT3TLjXTFbZdmqw,4427
7
6
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
7
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
8
  anydi/ext/_utils.py,sha256=U6sRqWzccWUu7eMhbXX1NrwcaxitQF9cO1KxnKF37gw,2566
10
9
  anydi/ext/fastapi.py,sha256=AEL3ubu-LxUPHMMt1YIn3En_JZC7nyBKmKxmhka3O3c,2436
11
10
  anydi/ext/faststream.py,sha256=qXnNGvAqWWp9kbhbQUE6EF_OPUiYQmtOH211_O7BI_0,1898
12
11
  anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
13
- anydi/ext/pytest_plugin.py,sha256=25A93Yon0cr4y8CszQB7zOJRiAXW-ZwtO7_Ji8mqcXs,4639
12
+ anydi/ext/pytest_plugin.py,sha256=yR_vos8qt8uFS9uy_G_HJjNgudzJIXawrtpWnn3Pu_s,4613
14
13
  anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
15
14
  anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
16
15
  anydi/ext/django/_settings.py,sha256=Z0RlAuXoO73oahWeMkK10w8c-4uCBde-DBpeKTV5USY,853
@@ -22,8 +21,8 @@ anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI
22
21
  anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfyk_NVEWw,2207
23
22
  anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
23
  anydi/ext/starlette/middleware.py,sha256=9CQtGg5ZzUz2gFSzJr8U4BWzwNjK8XMctm3n52M77Z0,792
25
- anydi-0.38.1.dist-info/METADATA,sha256=_DWptDh85lW1UwzL1AnIaCiB9THnpeaCI4AlupFYjG0,4917
26
- anydi-0.38.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- anydi-0.38.1.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
28
- anydi-0.38.1.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
29
- anydi-0.38.1.dist-info/RECORD,,
24
+ anydi-0.38.2rc1.dist-info/METADATA,sha256=1uaAnsVIgBRklI4VhtNU1PFIQacyBazRVqinXrF2ohc,4920
25
+ anydi-0.38.2rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ anydi-0.38.2rc1.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
27
+ anydi-0.38.2rc1.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
+ anydi-0.38.2rc1.dist-info/RECORD,,
anydi/_provider.py DELETED
@@ -1,232 +0,0 @@
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