python-injection 0.25.15__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 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] = ...,
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from collections.abc import Awaitable, Callable, MutableMapping
2
+ from collections.abc import Awaitable, Callable, MutableMapping, Sequence
3
3
  from contextlib import suppress
4
4
  from dataclasses import dataclass, field
5
5
  from functools import partial
@@ -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, get_scope, in_scope_cache
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
 
@@ -129,13 +129,13 @@ class ConstantInjectable[T](Injectable[T]):
129
129
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
130
130
  class ScopedInjectable[R, T](Injectable[T], ABC):
131
131
  factory: Caller[..., R]
132
- scope_name: str
132
+ scope_names: Sequence[str]
133
133
  key: SlotKey[T] = field(default_factory=SlotKey)
134
134
  logic: CacheLogic[T] = field(default_factory=CacheLogic)
135
135
 
136
136
  @property
137
137
  def is_locked(self) -> bool:
138
- return in_scope_cache(self.key, self.scope_name)
138
+ return in_scope_cache(self.key, *self.scope_names)
139
139
 
140
140
  @abstractmethod
141
141
  async def abuild(self, scope: Scope) -> T:
@@ -157,14 +157,16 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
157
157
 
158
158
  def unlock(self) -> None:
159
159
  if self.is_locked:
160
- raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
160
+ raise RuntimeError(
161
+ f"To unlock, close all open scopes in [{', '.join(f'`{name}`' for name in self.scope_names)}]."
162
+ )
161
163
 
162
164
  def __get_scope(self) -> Scope:
163
- return get_scope(self.scope_name)
165
+ return get_first_scope(*self.scope_names)
164
166
 
165
167
  @classmethod
166
- def bind_scope_name(cls, name: str) -> Callable[[Caller[..., R]], Self]:
167
- return partial(cls, scope_name=name)
168
+ def bind_scope_names(cls, names: Sequence[str]) -> Callable[[Caller[..., R]], Self]:
169
+ return partial(cls, scope_names=names)
168
170
 
169
171
 
170
172
  class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
@@ -211,7 +213,7 @@ class ScopedSlotInjectable[T](Injectable[T]):
211
213
 
212
214
  def get_instance(self) -> T:
213
215
  scope_name = self.scope_name
214
- scope = get_scope(scope_name)
216
+ scope = get_first_scope(scope_name)
215
217
 
216
218
  try:
217
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.bind_scope_name(scope_name),
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 get_scope(name: str, default: EllipsisType = ...) -> Scope: ...
154
+ def get_first_scope(*names: str, default: EllipsisType = ...) -> Scope: ...
155
155
 
156
156
  @overload
157
- def get_scope[T](name: str, default: T) -> Scope | T: ...
157
+ def get_first_scope[T](*names: str, default: T) -> Scope | T: ...
158
158
 
159
159
 
160
- def get_scope[T](name: str, default: T | EllipsisType = ...) -> Scope | T:
160
+ def get_first_scope[T](*names: str, default: T | EllipsisType = ...) -> Scope | T:
161
161
  for resolvers in __scope_resolvers.values():
162
- resolver = resolvers.get(name)
163
- if resolver and (scope := resolver.get_scope()):
164
- return scope
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"Scope `{name}` isn't defined in the current context."
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], scope_name: str) -> bool:
175
- return any(key in scope.cache for scope in iter_active_scopes(scope_name))
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(name: str) -> Iterator[Scope]:
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 get_scope(name, None):
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.25.15
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=GiGUBIkMhqN-TXEH_7iFrD8iQShIWwMXXyIcgSuGsfM,13667
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=eMrM_e9kFZIS8GkPZxBU1TMgehJ9Nw3hsnn9yonB_0A,6303
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=yc2fdgo-H8aY8FtYyaFMn_4vZbG-iFoRzK-e6m7GFjY,28736
13
- injection/_core/scope.py,sha256=FzR8msxnOjvr-uCVgqP71dCybxYstMJg207QvtRJ3HY,8369
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.25.15.dist-info/METADATA,sha256=O464FeMesuE4NlQN_OQI8BaEUaoBJ88ItAN1Ww17Zrc,3652
28
- python_injection-0.25.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
29
- python_injection-0.25.15.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
30
- python_injection-0.25.15.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.29.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any