anydi 0.38.0__py3-none-any.whl → 0.38.2rc0__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 | None = None
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
 
@@ -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:
@@ -57,27 +53,20 @@ def _anydi_should_inject(request: pytest.FixtureRequest) -> bool:
57
53
  return marker is not None or inject_all
58
54
 
59
55
 
60
- @pytest.fixture(scope="session")
61
- def _anydi_unresolved() -> Iterator[list[Any]]:
62
- unresolved: list[Any] = []
63
- yield unresolved
64
- unresolved.clear()
65
-
66
-
67
56
  @pytest.fixture
68
57
  def _anydi_injected_parameter_iterator(
69
58
  request: pytest.FixtureRequest,
70
- _anydi_unresolved: list[str],
71
59
  ) -> Callable[[], Iterator[tuple[str, Any]]]:
72
- registered_fixtures = request.session._fixturemanager._arg2fixturedefs # noqa
60
+ fixturenames = set(request.node._fixtureinfo.initialnames) - set(
61
+ request.node._fixtureinfo.name2fixturedefs.keys()
62
+ )
73
63
 
74
64
  def _iterator() -> Iterator[tuple[str, inspect.Parameter]]:
75
65
  for parameter in get_typed_parameters(request.function):
76
66
  interface = parameter.annotation
77
67
  if (
78
68
  interface is inspect.Parameter.empty
79
- or interface in _anydi_unresolved
80
- or parameter.name in registered_fixtures
69
+ or parameter.name not in fixturenames
81
70
  ):
82
71
  continue
83
72
  yield parameter.name, interface
@@ -90,7 +79,6 @@ def _anydi_inject(
90
79
  request: pytest.FixtureRequest,
91
80
  _anydi_should_inject: bool,
92
81
  _anydi_injected_parameter_iterator: Callable[[], Iterator[tuple[str, Any]]],
93
- _anydi_unresolved: list[str],
94
82
  ) -> None:
95
83
  """Inject dependencies into the test function."""
96
84
 
@@ -111,7 +99,6 @@ def _anydi_inject(
111
99
  logger.warning(
112
100
  f"Failed to resolve dependency for argument '{argname}'.", exc_info=exc
113
101
  )
114
- _anydi_unresolved.append(interface)
115
102
 
116
103
 
117
104
  @pytest.fixture(autouse=True)
@@ -119,7 +106,6 @@ def _anydi_ainject(
119
106
  request: pytest.FixtureRequest,
120
107
  _anydi_should_inject: bool,
121
108
  _anydi_injected_parameter_iterator: Callable[[], Iterator[tuple[str, Any]]],
122
- _anydi_unresolved: list[str],
123
109
  ) -> None:
124
110
  """Inject dependencies into the test function."""
125
111
  if (
@@ -149,7 +135,6 @@ def _anydi_ainject(
149
135
  f"Failed to resolve dependency for argument '{argname}'.",
150
136
  exc_info=exc,
151
137
  )
152
- _anydi_unresolved.append(interface)
153
138
 
154
139
  anyio_backend = request.getfixturevalue("anyio_backend")
155
140
  backend_name, backend_options = extract_backend_and_options(anyio_backend)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.38.0
3
+ Version: 0.38.2rc0
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,8 +1,7 @@
1
- anydi/__init__.py,sha256=OfRg2EfXD65pHTGQKhfkABMwUhw5LvsuTQV_Tv4V4wk,501
2
- anydi/_container.py,sha256=yH1DsSQFkbfxGaDFs47nr-iXLE_Wwgv6QMTGWrRb8cE,38524
1
+ anydi/__init__.py,sha256=aAq10a1V_zQ3_Me3p_pll5d1O77PIgqotkOm3pshORI,495
2
+ anydi/_container.py,sha256=BqpvUPeYt6OW7TLIDm-OvMGCbcxnvXA6KyOF9XBmi7M,43072
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
4
+ anydi/_types.py,sha256=oFyx6jxkEsz5FZk6tdRjUmBBQ2tX7eA_bLaa2elq7Mg,3586
6
5
  anydi/_utils.py,sha256=INI0jNIXrJ6LS4zqJymMO2yUEobpxmBGASf4G_vR6AU,4378
7
6
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
7
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -10,7 +9,7 @@ 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=ShGhiZnP1KyMHhnc9Ci1RKAuHVhw628OTS2P2BLEOfc,5001
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.0.dist-info/METADATA,sha256=MdihH1SW8w5R3GwJ1OGnFySPjC2Qkm9uKTwe1FvSICc,4917
26
- anydi-0.38.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- anydi-0.38.0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
28
- anydi-0.38.0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
29
- anydi-0.38.0.dist-info/RECORD,,
24
+ anydi-0.38.2rc0.dist-info/METADATA,sha256=t8FL1ILCUK37XUFQ_gqgAlwX77UytT5FeP4QueIDX50,4920
25
+ anydi-0.38.2rc0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ anydi-0.38.2rc0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
27
+ anydi-0.38.2rc0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
+ anydi-0.38.2rc0.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