python-injection 0.25.14__py3-none-any.whl → 0.26.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.
- injection/__init__.pyi +2 -2
- injection/_core/injectables.py +15 -34
- injection/_core/module.py +2 -3
- injection/_core/scope.py +13 -11
- {python_injection-0.25.14.dist-info → python_injection-0.26.0.dist-info}/METADATA +1 -1
- {python_injection-0.25.14.dist-info → python_injection-0.26.0.dist-info}/RECORD +8 -8
- {python_injection-0.25.14.dist-info → python_injection-0.26.0.dist-info}/WHEEL +1 -1
- {python_injection-0.25.14.dist-info → python_injection-0.26.0.dist-info}/licenses/LICENSE +0 -0
injection/__init__.pyi
CHANGED
|
@@ -68,6 +68,7 @@ def mod(name: str = ..., /) -> Module:
|
|
|
68
68
|
"""
|
|
69
69
|
Short syntax for `Module.from_name`.
|
|
70
70
|
"""
|
|
71
|
+
|
|
71
72
|
@runtime_checkable
|
|
72
73
|
class Injectable[T](Protocol):
|
|
73
74
|
@property
|
|
@@ -234,9 +235,8 @@ class Module:
|
|
|
234
235
|
) -> _Decorator: ...
|
|
235
236
|
def scoped(
|
|
236
237
|
self,
|
|
237
|
-
scope_name: str,
|
|
238
238
|
/,
|
|
239
|
-
|
|
239
|
+
*scope_names: str,
|
|
240
240
|
ignore_type_hint: bool = ...,
|
|
241
241
|
inject: bool = ...,
|
|
242
242
|
on: _TypeInfo[Any] = ...,
|
injection/_core/injectables.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from collections.abc import Awaitable, Callable,
|
|
3
|
-
from contextlib import
|
|
2
|
+
from collections.abc import Awaitable, Callable, MutableMapping, Sequence
|
|
3
|
+
from contextlib import suppress
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from functools import partial
|
|
6
6
|
from typing import (
|
|
@@ -16,7 +16,7 @@ from typing import (
|
|
|
16
16
|
|
|
17
17
|
from injection._core.common.asynchronous import AsyncSemaphore, Caller
|
|
18
18
|
from injection._core.common.type import InputType
|
|
19
|
-
from injection._core.scope import Scope,
|
|
19
|
+
from injection._core.scope import Scope, get_first_scope, in_scope_cache
|
|
20
20
|
from injection._core.slots import SlotKey
|
|
21
21
|
from injection.exceptions import EmptySlotError, InjectionError
|
|
22
22
|
|
|
@@ -53,13 +53,11 @@ class TransientInjectable[T](Injectable[T]):
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
class CacheLogic[T]:
|
|
56
|
-
__slots__ = ("
|
|
56
|
+
__slots__ = ("__semaphore",)
|
|
57
57
|
|
|
58
|
-
__is_instantiating: bool
|
|
59
58
|
__semaphore: AsyncContextManager[Any]
|
|
60
59
|
|
|
61
60
|
def __init__(self) -> None:
|
|
62
|
-
self.__is_instantiating = False
|
|
63
61
|
self.__semaphore = AsyncSemaphore(1)
|
|
64
62
|
|
|
65
63
|
async def aget_or_create[K](
|
|
@@ -68,14 +66,11 @@ class CacheLogic[T]:
|
|
|
68
66
|
key: K,
|
|
69
67
|
factory: Callable[..., Awaitable[T]],
|
|
70
68
|
) -> T:
|
|
71
|
-
self.__fail_if_instantiating()
|
|
72
69
|
async with self.__semaphore:
|
|
73
70
|
with suppress(KeyError):
|
|
74
71
|
return cache[key]
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
instance = await factory()
|
|
78
|
-
|
|
73
|
+
instance = await factory()
|
|
79
74
|
cache[key] = instance
|
|
80
75
|
|
|
81
76
|
return instance
|
|
@@ -86,29 +81,13 @@ class CacheLogic[T]:
|
|
|
86
81
|
key: K,
|
|
87
82
|
factory: Callable[..., T],
|
|
88
83
|
) -> T:
|
|
89
|
-
self.__fail_if_instantiating()
|
|
90
84
|
with suppress(KeyError):
|
|
91
85
|
return cache[key]
|
|
92
86
|
|
|
93
|
-
|
|
94
|
-
instance = factory()
|
|
95
|
-
|
|
87
|
+
instance = factory()
|
|
96
88
|
cache[key] = instance
|
|
97
89
|
return instance
|
|
98
90
|
|
|
99
|
-
def __fail_if_instantiating(self) -> None:
|
|
100
|
-
if self.__is_instantiating:
|
|
101
|
-
raise RecursionError("Recursive call detected during instantiation.")
|
|
102
|
-
|
|
103
|
-
@contextmanager
|
|
104
|
-
def __instantiating(self) -> Iterator[None]:
|
|
105
|
-
self.__is_instantiating = True
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
yield
|
|
109
|
-
finally:
|
|
110
|
-
self.__is_instantiating = False
|
|
111
|
-
|
|
112
91
|
|
|
113
92
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
114
93
|
class SingletonInjectable[T](Injectable[T]):
|
|
@@ -150,13 +129,13 @@ class ConstantInjectable[T](Injectable[T]):
|
|
|
150
129
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
151
130
|
class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
152
131
|
factory: Caller[..., R]
|
|
153
|
-
|
|
132
|
+
scope_names: Sequence[str]
|
|
154
133
|
key: SlotKey[T] = field(default_factory=SlotKey)
|
|
155
134
|
logic: CacheLogic[T] = field(default_factory=CacheLogic)
|
|
156
135
|
|
|
157
136
|
@property
|
|
158
137
|
def is_locked(self) -> bool:
|
|
159
|
-
return in_scope_cache(self.key, self.
|
|
138
|
+
return in_scope_cache(self.key, *self.scope_names)
|
|
160
139
|
|
|
161
140
|
@abstractmethod
|
|
162
141
|
async def abuild(self, scope: Scope) -> T:
|
|
@@ -178,14 +157,16 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
|
178
157
|
|
|
179
158
|
def unlock(self) -> None:
|
|
180
159
|
if self.is_locked:
|
|
181
|
-
raise RuntimeError(
|
|
160
|
+
raise RuntimeError(
|
|
161
|
+
f"To unlock, close all open scopes in [{', '.join(f'`{name}`' for name in self.scope_names)}]."
|
|
162
|
+
)
|
|
182
163
|
|
|
183
164
|
def __get_scope(self) -> Scope:
|
|
184
|
-
return
|
|
165
|
+
return get_first_scope(*self.scope_names)
|
|
185
166
|
|
|
186
167
|
@classmethod
|
|
187
|
-
def
|
|
188
|
-
return partial(cls,
|
|
168
|
+
def bind_scope_names(cls, names: Sequence[str]) -> Callable[[Caller[..., R]], Self]:
|
|
169
|
+
return partial(cls, scope_names=names)
|
|
189
170
|
|
|
190
171
|
|
|
191
172
|
class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
|
|
@@ -232,7 +213,7 @@ class ScopedSlotInjectable[T](Injectable[T]):
|
|
|
232
213
|
|
|
233
214
|
def get_instance(self) -> T:
|
|
234
215
|
scope_name = self.scope_name
|
|
235
|
-
scope =
|
|
216
|
+
scope = get_first_scope(scope_name)
|
|
236
217
|
|
|
237
218
|
try:
|
|
238
219
|
return scope.cache[self.key]
|
injection/_core/module.py
CHANGED
|
@@ -250,9 +250,8 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
|
|
|
250
250
|
|
|
251
251
|
def scoped[**P, T](
|
|
252
252
|
self,
|
|
253
|
-
scope_name: str,
|
|
254
253
|
/,
|
|
255
|
-
|
|
254
|
+
*scope_names: str,
|
|
256
255
|
ignore_type_hint: bool = False,
|
|
257
256
|
inject: bool = True,
|
|
258
257
|
on: TypeInfo[T] = (),
|
|
@@ -284,7 +283,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
|
|
|
284
283
|
|
|
285
284
|
self.injectable(
|
|
286
285
|
ctx.wrapper,
|
|
287
|
-
cls=ctx.cls.
|
|
286
|
+
cls=ctx.cls.bind_scope_names(scope_names),
|
|
288
287
|
ignore_type_hint=True,
|
|
289
288
|
inject=inject,
|
|
290
289
|
on=(*ctx.hints, on),
|
injection/_core/scope.py
CHANGED
|
@@ -151,34 +151,36 @@ def define_scope(
|
|
|
151
151
|
if TYPE_CHECKING: # pragma: no cover
|
|
152
152
|
|
|
153
153
|
@overload
|
|
154
|
-
def
|
|
154
|
+
def get_first_scope(*names: str, default: EllipsisType = ...) -> Scope: ...
|
|
155
155
|
|
|
156
156
|
@overload
|
|
157
|
-
def
|
|
157
|
+
def get_first_scope[T](*names: str, default: T) -> Scope | T: ...
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def
|
|
160
|
+
def get_first_scope[T](*names: str, default: T | EllipsisType = ...) -> Scope | T:
|
|
161
161
|
for resolvers in __scope_resolvers.values():
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
for name in names:
|
|
163
|
+
resolver = resolvers.get(name)
|
|
164
|
+
if resolver and (scope := resolver.get_scope()):
|
|
165
|
+
return scope
|
|
165
166
|
|
|
166
167
|
if default is ...:
|
|
167
168
|
raise ScopeUndefinedError(
|
|
168
|
-
f"
|
|
169
|
+
f"No scope in [{', '.join(f'`{name}`' for name in names)}] is defined in the current context."
|
|
169
170
|
)
|
|
170
171
|
|
|
171
172
|
return default
|
|
172
173
|
|
|
173
174
|
|
|
174
|
-
def in_scope_cache(key: SlotKey[Any],
|
|
175
|
-
return any(key in scope.cache for scope in iter_active_scopes(
|
|
175
|
+
def in_scope_cache(key: SlotKey[Any], *scope_names: str) -> bool:
|
|
176
|
+
return any(key in scope.cache for scope in iter_active_scopes(*scope_names))
|
|
176
177
|
|
|
177
178
|
|
|
178
|
-
def iter_active_scopes(
|
|
179
|
+
def iter_active_scopes(*names: str) -> Iterator[Scope]:
|
|
179
180
|
active_scopes = (
|
|
180
181
|
resolver.active_scopes
|
|
181
182
|
for resolvers in __scope_resolvers.values()
|
|
183
|
+
for name in names
|
|
182
184
|
if (resolver := resolvers.get(name))
|
|
183
185
|
)
|
|
184
186
|
return itertools.chain.from_iterable(active_scopes)
|
|
@@ -194,7 +196,7 @@ def _bind_scope(
|
|
|
194
196
|
lock = get_lock(threadsafe)
|
|
195
197
|
|
|
196
198
|
with lock:
|
|
197
|
-
if
|
|
199
|
+
if get_first_scope(name, default=None):
|
|
198
200
|
raise ScopeAlreadyDefinedError(
|
|
199
201
|
f"Scope `{name}` is already defined in the current context."
|
|
200
202
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-injection
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Dead-simple dependency injection framework for Python.
|
|
5
5
|
Project-URL: Documentation, https://python-injection.remimd.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/100nm/python-injection
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
injection/__init__.py,sha256=8nYCdToksoBIRTiJFrEbAUa_g8--dFL--4pfSGlGDP4,1350
|
|
2
|
-
injection/__init__.pyi,sha256=
|
|
2
|
+
injection/__init__.pyi,sha256=XzOmrlk_6HNAiQVU4aUDoy3414enMaKTNPpKpXHLYyI,13659
|
|
3
3
|
injection/entrypoint.py,sha256=SjviVj8ajEybGsZ_hmm_NkPIHrIOTmnt3Uhg_GAQJ-s,6709
|
|
4
4
|
injection/exceptions.py,sha256=v57yMujiq6H_zwwn30A8UYEZX9R9k-bY8FnsdaimPM4,1025
|
|
5
5
|
injection/loaders.py,sha256=gBwEfmR1ZHybD2hdtVr-ji6HZS7gCQvgajoiatmZpeM,7394
|
|
@@ -7,10 +7,10 @@ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
7
7
|
injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
injection/_core/asfunction.py,sha256=GY228WHQWvA-9rg3unR8ttyIJlhwxy0bC3LUO5K8Erg,1864
|
|
9
9
|
injection/_core/descriptors.py,sha256=wpW6lFz7ZDDOh1Vc7P86z49UkZ1lajBReWOagSotWDs,3574
|
|
10
|
-
injection/_core/injectables.py,sha256=
|
|
10
|
+
injection/_core/injectables.py,sha256=t_A-tP7kNmNgjXUX_TKwZj3NU65RDpMUTqkVtvXGVNw,6435
|
|
11
11
|
injection/_core/locator.py,sha256=pff_amjMi0XNXdIcPG8fK3vefRPwVLiZVvFg0qwEvDg,8258
|
|
12
|
-
injection/_core/module.py,sha256=
|
|
13
|
-
injection/_core/scope.py,sha256=
|
|
12
|
+
injection/_core/module.py,sha256=AJgAtoYmAzpCXMiazk2mlyW1aAiGuHK5XcvC5Vf-Ibo,28729
|
|
13
|
+
injection/_core/scope.py,sha256=959UhEt1zY2lstP7zJgQJPGMv4s2fdv0gRL4f47GqRM,8517
|
|
14
14
|
injection/_core/slots.py,sha256=g9TG6CbqRzCsjg01iPyfRtTTUCJnnJOwcj9mJabH0dc,37
|
|
15
15
|
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
injection/_core/common/asynchronous.py,sha256=nKY2_PAMFF2haC75Fwp8O9Zd-SDuenE6n3B-KccwXlw,1772
|
|
@@ -24,7 +24,7 @@ injection/ext/fastapi.py,sha256=wpiNyHKEPwQ7sAJt30XpkgiCi4-gfmyqkQWw9BejXwg,1633
|
|
|
24
24
|
injection/ext/fastapi.pyi,sha256=HLs7mfruIEFRrN_Xf8oCvSa4qwHWfwm6HHU_KMedXkE,185
|
|
25
25
|
injection/testing/__init__.py,sha256=ZWMacZuGhK8Edq1L0Ng0PaRlgKXEunyFZkFvx7-IYIQ,935
|
|
26
26
|
injection/testing/__init__.pyi,sha256=ZFQMUZmvBVkV6Ch7jiYE9zeJhnqHAQnt3vWQ1Th0zDk,646
|
|
27
|
-
python_injection-0.
|
|
28
|
-
python_injection-0.
|
|
29
|
-
python_injection-0.
|
|
30
|
-
python_injection-0.
|
|
27
|
+
python_injection-0.26.0.dist-info/METADATA,sha256=FzRj240iIOWO8HPpTxuAbpEYm-tmBkLHLRbADuoGQJw,3651
|
|
28
|
+
python_injection-0.26.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
29
|
+
python_injection-0.26.0.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
|
|
30
|
+
python_injection-0.26.0.dist-info/RECORD,,
|
|
File without changes
|