python-injection 0.12.3__py3-none-any.whl → 0.13.1__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__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from ._core.descriptors import LazyInstance
2
2
  from ._core.injectables import Injectable
3
3
  from ._core.module import Mode, Module, Priority, mod
4
+ from ._core.scope import adefine_scope, define_scope
4
5
 
5
6
  __all__ = (
6
7
  "Injectable",
@@ -8,16 +9,19 @@ __all__ = (
8
9
  "Mode",
9
10
  "Module",
10
11
  "Priority",
12
+ "adefine_scope",
11
13
  "afind_instance",
12
14
  "aget_instance",
13
15
  "aget_lazy_instance",
14
16
  "constant",
17
+ "define_scope",
15
18
  "find_instance",
16
19
  "get_instance",
17
20
  "get_lazy_instance",
18
21
  "inject",
19
22
  "injectable",
20
23
  "mod",
24
+ "scoped",
21
25
  "set_constant",
22
26
  "should_be_injectable",
23
27
  "singleton",
@@ -32,6 +36,7 @@ get_instance = mod().get_instance
32
36
  get_lazy_instance = mod().get_lazy_instance
33
37
  inject = mod().inject
34
38
  injectable = mod().injectable
39
+ scoped = mod().scoped
35
40
  set_constant = mod().set_constant
36
41
  should_be_injectable = mod().should_be_injectable
37
42
  singleton = mod().singleton
injection/__init__.pyi CHANGED
@@ -1,17 +1,9 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import Awaitable, Callable
3
- from contextlib import ContextDecorator
2
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
3
+ from contextlib import asynccontextmanager, contextmanager
4
4
  from enum import Enum
5
5
  from logging import Logger
6
- from typing import (
7
- Any,
8
- ContextManager,
9
- Protocol,
10
- Self,
11
- final,
12
- overload,
13
- runtime_checkable,
14
- )
6
+ from typing import Any, Final, Protocol, Self, final, overload, runtime_checkable
15
7
 
16
8
  from ._core.common.invertible import Invertible as _Invertible
17
9
  from ._core.common.type import InputType as _InputType
@@ -19,27 +11,53 @@ from ._core.common.type import TypeInfo as _TypeInfo
19
11
  from ._core.module import InjectableFactory as _InjectableFactory
20
12
  from ._core.module import ModeStr, PriorityStr
21
13
 
22
- _: Module = ...
14
+ __MODULE: Final[Module] = ...
23
15
 
24
- afind_instance = _.afind_instance
25
- aget_instance = _.aget_instance
26
- aget_lazy_instance = _.aget_lazy_instance
27
- constant = _.constant
28
- find_instance = _.find_instance
29
- get_instance = _.get_instance
30
- get_lazy_instance = _.get_lazy_instance
31
- inject = _.inject
32
- injectable = _.injectable
33
- set_constant = _.set_constant
34
- should_be_injectable = _.should_be_injectable
35
- singleton = _.singleton
36
-
37
- del _
16
+ afind_instance = __MODULE.afind_instance
17
+ aget_instance = __MODULE.aget_instance
18
+ aget_lazy_instance = __MODULE.aget_lazy_instance
19
+ constant = __MODULE.constant
20
+ find_instance = __MODULE.find_instance
21
+ get_instance = __MODULE.get_instance
22
+ get_lazy_instance = __MODULE.get_lazy_instance
23
+ inject = __MODULE.inject
24
+ injectable = __MODULE.injectable
25
+ scoped = __MODULE.scoped
26
+ set_constant = __MODULE.set_constant
27
+ should_be_injectable = __MODULE.should_be_injectable
28
+ singleton = __MODULE.singleton
38
29
 
30
+ @asynccontextmanager
31
+ def adefine_scope(name: str, *, shared: bool = ...) -> AsyncIterator[None]: ...
32
+ @contextmanager
33
+ def define_scope(name: str, *, shared: bool = ...) -> Iterator[None]: ...
39
34
  def mod(name: str = ..., /) -> Module:
40
35
  """
41
36
  Short syntax for `Module.from_name`.
42
37
  """
38
+ @runtime_checkable
39
+ class Injectable[T](Protocol):
40
+ @property
41
+ def is_locked(self) -> bool: ...
42
+ def unlock(self) -> None: ...
43
+ @abstractmethod
44
+ async def aget_instance(self) -> T: ...
45
+ @abstractmethod
46
+ def get_instance(self) -> T: ...
47
+
48
+ class LazyInstance[T]:
49
+ def __init__(
50
+ self,
51
+ cls: _InputType[T],
52
+ /,
53
+ default: T = ...,
54
+ module: Module = ...,
55
+ ) -> None: ...
56
+ @overload
57
+ def __get__(self, instance: object, owner: type | None = ...) -> T: ...
58
+ @overload
59
+ def __get__(self, instance: None = ..., owner: type | None = ...) -> Self: ...
60
+
43
61
  @final
44
62
  class Module:
45
63
  """
@@ -94,6 +112,21 @@ class Module:
94
112
  always be the same.
95
113
  """
96
114
 
115
+ def scoped[**P, T](
116
+ self,
117
+ scope_name: str,
118
+ /,
119
+ *,
120
+ inject: bool = ...,
121
+ on: _TypeInfo[T] = (),
122
+ mode: Mode | ModeStr = ...,
123
+ ) -> Any:
124
+ """
125
+ Decorator applicable to a class or function or generator function. It is used
126
+ to indicate how the scoped instance will be constructed. At injection time, the
127
+ injected instance is retrieved from the scope.
128
+ """
129
+
97
130
  def should_be_injectable[T](self, wrapped: type[T] = ..., /) -> Any:
98
131
  """
99
132
  Decorator applicable to a class. It is used to specify whether an injectable
@@ -233,12 +266,13 @@ class Module:
233
266
  Function to remove a module in use.
234
267
  """
235
268
 
269
+ @contextmanager
236
270
  def use_temporarily(
237
271
  self,
238
272
  module: Module,
239
273
  *,
240
274
  priority: Priority | PriorityStr = ...,
241
- ) -> ContextManager[None] | ContextDecorator:
275
+ ) -> Iterator[None]:
242
276
  """
243
277
  Context manager or decorator for temporary use of a module.
244
278
  """
@@ -275,30 +309,13 @@ class Module:
275
309
  Class method for getting the default module.
276
310
  """
277
311
 
278
- @final
279
- class Priority(Enum):
280
- LOW = ...
281
- HIGH = ...
282
-
283
- @runtime_checkable
284
- class Injectable[T](Protocol):
285
- @property
286
- def is_locked(self) -> bool: ...
287
- def unlock(self) -> None: ...
288
- @abstractmethod
289
- async def aget_instance(self) -> T: ...
290
- @abstractmethod
291
- def get_instance(self) -> T: ...
292
-
293
312
  @final
294
313
  class Mode(Enum):
295
314
  FALLBACK = ...
296
315
  NORMAL = ...
297
316
  OVERRIDE = ...
298
317
 
299
- class LazyInstance[T]:
300
- def __init__(self, cls: _InputType[T], module: Module = ...) -> None: ...
301
- @overload
302
- def __get__(self, instance: object, owner: type | None = ...) -> T: ...
303
- @overload
304
- def __get__(self, instance: None = ..., owner: type | None = ...) -> Self: ...
318
+ @final
319
+ class Priority(Enum):
320
+ LOW = ...
321
+ HIGH = ...
@@ -1,10 +1,19 @@
1
1
  import asyncio
2
2
  from abc import abstractmethod
3
- from collections.abc import Awaitable, Callable, Generator
3
+ from collections.abc import Awaitable, Callable, Coroutine, Generator
4
4
  from dataclasses import dataclass
5
5
  from typing import Any, Protocol, runtime_checkable
6
6
 
7
7
 
8
+ def run_sync[T](coroutine: Coroutine[Any, Any, T]) -> T:
9
+ loop = asyncio.get_event_loop()
10
+
11
+ try:
12
+ return loop.run_until_complete(coroutine)
13
+ finally:
14
+ coroutine.close()
15
+
16
+
8
17
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
9
18
  class SimpleAwaitable[T](Awaitable[T]):
10
19
  callable: Callable[..., Awaitable[T]]
@@ -28,21 +37,13 @@ class Caller[**P, T](Protocol):
28
37
 
29
38
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
30
39
  class AsyncCaller[**P, T](Caller[P, T]):
31
- callable: Callable[P, Awaitable[T]]
40
+ callable: Callable[P, Coroutine[Any, Any, T]]
32
41
 
33
42
  async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
34
43
  return await self.callable(*args, **kwargs)
35
44
 
36
45
  def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
37
- loop = asyncio.get_event_loop()
38
-
39
- if loop.is_running():
40
- raise RuntimeError(
41
- "Can't call an asynchronous function in a synchronous context."
42
- )
43
-
44
- coroutine = self.callable(*args, **kwargs)
45
- return loop.run_until_complete(coroutine)
46
+ return run_sync(self.callable(*args, **kwargs))
46
47
 
47
48
 
48
49
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -25,7 +25,7 @@ class EventChannel:
25
25
  @contextmanager
26
26
  def dispatch(self, event: Event) -> Iterator[None]:
27
27
  with ExitStack() as stack:
28
- for listener in tuple(self.__listeners):
28
+ for listener in self.__listeners:
29
29
  context_manager = listener.on_event(event)
30
30
 
31
31
  if context_manager is None:
@@ -0,0 +1,5 @@
1
+ from uuid import uuid4
2
+
3
+
4
+ def new_short_key() -> str:
5
+ return uuid4().hex[:7]
@@ -1,4 +1,12 @@
1
- from collections.abc import Callable, Iterable, Iterator
1
+ from collections.abc import (
2
+ AsyncGenerator,
3
+ AsyncIterable,
4
+ AsyncIterator,
5
+ Callable,
6
+ Generator,
7
+ Iterable,
8
+ Iterator,
9
+ )
2
10
  from inspect import isfunction
3
11
  from types import GenericAlias, UnionType
4
12
  from typing import (
@@ -23,7 +31,7 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
23
31
  ):
24
32
  inner_args = arg
25
33
 
26
- elif isfunction(arg) and (return_type := get_type_hints(arg).get("return")):
34
+ elif isfunction(arg) and (return_type := get_return_hint(arg)):
27
35
  inner_args = (return_type,)
28
36
 
29
37
  else:
@@ -33,6 +41,29 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
33
41
  yield from get_return_types(*inner_args)
34
42
 
35
43
 
44
+ def get_return_hint[T](function: Callable[..., T]) -> InputType[T] | None:
45
+ return get_type_hints(function).get("return")
46
+
47
+
48
+ def get_yield_hint[T](
49
+ function: Callable[..., Iterator[T]] | Callable[..., AsyncIterator[T]],
50
+ ) -> InputType[T] | None:
51
+ return_type = get_return_hint(function)
52
+
53
+ if get_origin(return_type) not in {
54
+ AsyncGenerator,
55
+ AsyncIterable,
56
+ AsyncIterator,
57
+ Generator,
58
+ Iterable,
59
+ Iterator,
60
+ }:
61
+ return None
62
+
63
+ args = get_args(return_type)
64
+ return next(iter(args), None)
65
+
66
+
36
67
  def standardize_types(
37
68
  *types: InputType[Any],
38
69
  with_origin: bool = False,
@@ -10,9 +10,15 @@ class LazyInstance[T]:
10
10
 
11
11
  __value: Invertible[T]
12
12
 
13
- def __init__(self, cls: InputType[T], module: Module | None = None) -> None:
13
+ def __init__(
14
+ self,
15
+ cls: InputType[T],
16
+ /,
17
+ default: T = NotImplemented,
18
+ module: Module | None = None,
19
+ ) -> None:
14
20
  module = module or mod()
15
- self.__value = module.get_lazy_instance(cls, default=NotImplemented)
21
+ self.__value = module.get_lazy_instance(cls, default)
16
22
 
17
23
  def __get__(
18
24
  self,
@@ -2,10 +2,18 @@ from abc import ABC, abstractmethod
2
2
  from collections.abc import MutableMapping
3
3
  from contextlib import suppress
4
4
  from dataclasses import dataclass
5
- from typing import Any, ClassVar, NoReturn, Protocol, runtime_checkable
6
-
7
- from injection._core.common.asynchronous import Caller
8
- from injection._core.common.threading import synchronized
5
+ from typing import (
6
+ Any,
7
+ AsyncContextManager,
8
+ ClassVar,
9
+ ContextManager,
10
+ NoReturn,
11
+ Protocol,
12
+ runtime_checkable,
13
+ )
14
+
15
+ from injection._core.common.asynchronous import Caller, run_sync
16
+ from injection._core.scope import Scope, get_active_scopes, get_scope
9
17
  from injection.exceptions import InjectionError
10
18
 
11
19
 
@@ -50,41 +58,114 @@ class SingletonInjectable[T](BaseInjectable[T]):
50
58
  __key: ClassVar[str] = "$instance"
51
59
 
52
60
  @property
53
- def cache(self) -> MutableMapping[str, Any]:
61
+ def is_locked(self) -> bool:
62
+ return self.__key in self.__cache
63
+
64
+ @property
65
+ def __cache(self) -> MutableMapping[str, Any]:
54
66
  return self.__dict__
55
67
 
68
+ async def aget_instance(self) -> T:
69
+ cache = self.__cache
70
+
71
+ with suppress(KeyError):
72
+ return cache[self.__key]
73
+
74
+ instance = await self.factory.acall()
75
+ cache[self.__key] = instance
76
+ return instance
77
+
78
+ def get_instance(self) -> T:
79
+ cache = self.__cache
80
+
81
+ with suppress(KeyError):
82
+ return cache[self.__key]
83
+
84
+ instance = self.factory.call()
85
+ cache[self.__key] = instance
86
+ return instance
87
+
88
+ def unlock(self) -> None:
89
+ self.__cache.pop(self.__key, None)
90
+
91
+
92
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
93
+ class ScopedInjectable[R, T](Injectable[T], ABC):
94
+ factory: Caller[..., R]
95
+ scope_name: str
96
+
56
97
  @property
57
98
  def is_locked(self) -> bool:
58
- return self.__key in self.cache
99
+ return any(self in scope.cache for scope in get_active_scopes(self.scope_name))
59
100
 
60
- def unlock(self) -> None:
61
- self.cache.clear()
101
+ @abstractmethod
102
+ async def abuild(self, scope: Scope) -> T:
103
+ raise NotImplementedError
104
+
105
+ @abstractmethod
106
+ def build(self, scope: Scope) -> T:
107
+ raise NotImplementedError
62
108
 
63
109
  async def aget_instance(self) -> T:
64
- with suppress(KeyError):
65
- return self.__check_instance()
110
+ scope = get_scope(self.scope_name)
66
111
 
67
- with synchronized():
68
- instance = await self.factory.acall()
69
- self.__set_instance(instance)
112
+ with suppress(KeyError):
113
+ return scope.cache[self]
70
114
 
115
+ instance = await self.abuild(scope)
116
+ scope.cache[self] = instance
71
117
  return instance
72
118
 
73
119
  def get_instance(self) -> T:
74
- with suppress(KeyError):
75
- return self.__check_instance()
120
+ scope = get_scope(self.scope_name)
76
121
 
77
- with synchronized():
78
- instance = self.factory.call()
79
- self.__set_instance(instance)
122
+ with suppress(KeyError):
123
+ return scope.cache[self]
80
124
 
125
+ instance = self.build(scope)
126
+ scope.cache[self] = instance
81
127
  return instance
82
128
 
83
- def __check_instance(self) -> T:
84
- return self.cache[self.__key]
129
+ def unlock(self) -> None:
130
+ if self.is_locked:
131
+ raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
132
+
133
+
134
+ class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
135
+ __slots__ = ()
136
+
137
+ async def abuild(self, scope: Scope) -> T:
138
+ cm = await self.factory.acall()
139
+ return await scope.aenter(cm)
140
+
141
+ def build(self, scope: Scope) -> T:
142
+ return run_sync(self.abuild(scope))
143
+
85
144
 
86
- def __set_instance(self, value: T) -> None:
87
- self.cache[self.__key] = value
145
+ class CMScopedInjectable[T](ScopedInjectable[ContextManager[T], T]):
146
+ __slots__ = ()
147
+
148
+ async def abuild(self, scope: Scope) -> T:
149
+ cm = await self.factory.acall()
150
+ return scope.enter(cm)
151
+
152
+ def build(self, scope: Scope) -> T:
153
+ cm = self.factory.call()
154
+ return scope.enter(cm)
155
+
156
+
157
+ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
158
+ __slots__ = ()
159
+
160
+ async def abuild(self, scope: Scope) -> T:
161
+ return await self.factory.acall()
162
+
163
+ def build(self, scope: Scope) -> T:
164
+ return self.factory.call()
165
+
166
+ def unlock(self) -> None:
167
+ for scope in get_active_scopes(self.scope_name):
168
+ scope.cache.pop(self, None)
88
169
 
89
170
 
90
171
  @dataclass(repr=False, frozen=True, slots=True)
injection/_core/module.py CHANGED
@@ -12,17 +12,25 @@ from collections.abc import (
12
12
  Iterator,
13
13
  Mapping,
14
14
  )
15
- from contextlib import contextmanager, suppress
15
+ from contextlib import asynccontextmanager, contextmanager, suppress
16
16
  from dataclasses import dataclass, field
17
17
  from enum import StrEnum
18
18
  from functools import partialmethod, singledispatchmethod, update_wrapper
19
- from inspect import Signature, isclass, iscoroutinefunction, markcoroutinefunction
19
+ from inspect import (
20
+ Signature,
21
+ isasyncgenfunction,
22
+ isclass,
23
+ iscoroutinefunction,
24
+ isgeneratorfunction,
25
+ markcoroutinefunction,
26
+ )
20
27
  from inspect import signature as inspect_signature
21
28
  from logging import Logger, getLogger
22
29
  from queue import Empty, Queue
23
30
  from types import MethodType
24
31
  from typing import (
25
32
  Any,
33
+ AsyncContextManager,
26
34
  ClassVar,
27
35
  ContextManager,
28
36
  Literal,
@@ -32,7 +40,6 @@ from typing import (
32
40
  overload,
33
41
  runtime_checkable,
34
42
  )
35
- from uuid import uuid4
36
43
 
37
44
  from injection._core.common.asynchronous import (
38
45
  AsyncCaller,
@@ -42,14 +49,22 @@ from injection._core.common.asynchronous import (
42
49
  )
43
50
  from injection._core.common.event import Event, EventChannel, EventListener
44
51
  from injection._core.common.invertible import Invertible, SimpleInvertible
52
+ from injection._core.common.key import new_short_key
45
53
  from injection._core.common.lazy import Lazy, LazyMapping
46
- from injection._core.common.threading import synchronized
47
- from injection._core.common.type import InputType, TypeInfo, get_return_types
54
+ from injection._core.common.type import (
55
+ InputType,
56
+ TypeInfo,
57
+ get_return_types,
58
+ get_yield_hint,
59
+ )
48
60
  from injection._core.hook import Hook, apply_hooks
49
61
  from injection._core.injectables import (
62
+ AsyncCMScopedInjectable,
63
+ CMScopedInjectable,
50
64
  Injectable,
51
65
  ShouldBeInjectable,
52
66
  SimpleInjectable,
67
+ SimpleScopedInjectable,
53
68
  SingletonInjectable,
54
69
  )
55
70
  from injection.exceptions import (
@@ -198,14 +213,12 @@ class Record[T](NamedTuple):
198
213
 
199
214
  @dataclass(repr=False, eq=False, kw_only=True, slots=True)
200
215
  class Updater[T]:
201
- factory: Caller[..., T]
202
216
  classes: Iterable[InputType[T]]
203
- injectable_factory: InjectableFactory[T]
217
+ injectable: Injectable[T]
204
218
  mode: Mode
205
219
 
206
220
  def make_record(self) -> Record[T]:
207
- injectable = self.injectable_factory(self.factory)
208
- return Record(injectable, self.mode)
221
+ return Record(self.injectable, self.mode)
209
222
 
210
223
 
211
224
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -262,7 +275,6 @@ class Locator(Broker):
262
275
  def __injectables(self) -> frozenset[Injectable[Any]]:
263
276
  return frozenset(record.injectable for record in self.__records.values())
264
277
 
265
- @synchronized()
266
278
  def update[T](self, updater: Updater[T]) -> Self:
267
279
  updater = self.__update_preprocessing(updater)
268
280
  record = updater.make_record()
@@ -276,7 +288,6 @@ class Locator(Broker):
276
288
 
277
289
  return self
278
290
 
279
- @synchronized()
280
291
  def unlock(self) -> Self:
281
292
  for injectable in self.__injectables:
282
293
  injectable.unlock()
@@ -350,7 +361,7 @@ type PriorityStr = Literal["low", "high"]
350
361
 
351
362
  @dataclass(eq=False, frozen=True, slots=True)
352
363
  class Module(Broker, EventListener):
353
- name: str = field(default_factory=lambda: f"anonymous@{uuid4().hex[:7]}")
364
+ name: str = field(default_factory=lambda: f"anonymous@{new_short_key()}")
354
365
  __channel: EventChannel = field(
355
366
  default_factory=EventChannel,
356
367
  init=False,
@@ -393,7 +404,7 @@ class Module(Broker, EventListener):
393
404
 
394
405
  @property
395
406
  def __brokers(self) -> Iterator[Broker]:
396
- yield from tuple(self.__modules)
407
+ yield from self.__modules
397
408
  yield self.__locator
398
409
 
399
410
  def injectable[**P, T](
@@ -402,6 +413,7 @@ class Module(Broker, EventListener):
402
413
  /,
403
414
  *,
404
415
  cls: InjectableFactory[T] = SimpleInjectable,
416
+ ignore_type_hint: bool = False,
405
417
  inject: bool = True,
406
418
  on: TypeInfo[T] = (),
407
419
  mode: Mode | ModeStr = Mode.get_default(),
@@ -409,12 +421,11 @@ class Module(Broker, EventListener):
409
421
  def decorator(
410
422
  wp: Callable[P, T] | Callable[P, Awaitable[T]],
411
423
  ) -> Callable[P, T] | Callable[P, Awaitable[T]]:
412
- factory = _get_caller(self.make_injected_function(wp) if inject else wp)
413
- classes = get_return_types(wp, on)
424
+ factory = extract_caller(self.make_injected_function(wp) if inject else wp)
425
+ hints = on if ignore_type_hint else (wp, on)
414
426
  updater = Updater(
415
- factory=factory,
416
- classes=classes,
417
- injectable_factory=cls,
427
+ classes=get_return_types(hints),
428
+ injectable=cls(factory), # type: ignore[arg-type]
418
429
  mode=Mode(mode),
419
430
  )
420
431
  self.update(updater)
@@ -424,12 +435,66 @@ class Module(Broker, EventListener):
424
435
 
425
436
  singleton = partialmethod(injectable, cls=SingletonInjectable)
426
437
 
438
+ def scoped[**P, T](
439
+ self,
440
+ scope_name: str,
441
+ /,
442
+ *,
443
+ inject: bool = True,
444
+ on: TypeInfo[T] = (),
445
+ mode: Mode | ModeStr = Mode.get_default(),
446
+ ) -> Any:
447
+ def decorator(
448
+ wrapped: Callable[P, T]
449
+ | Callable[P, Awaitable[T]]
450
+ | Callable[P, Iterator[T]]
451
+ | Callable[P, AsyncIterator[T]],
452
+ ) -> (
453
+ Callable[P, T]
454
+ | Callable[P, Awaitable[T]]
455
+ | Callable[P, Iterator[T]]
456
+ | Callable[P, AsyncIterator[T]]
457
+ ):
458
+ injectable_class: Callable[[Caller[P, Any], str], Injectable[T]]
459
+ wrapper: (
460
+ Callable[P, T]
461
+ | Callable[P, Awaitable[T]]
462
+ | Callable[P, ContextManager[T]]
463
+ | Callable[P, AsyncContextManager[T]]
464
+ )
465
+
466
+ if isasyncgenfunction(wrapped):
467
+ hint = get_yield_hint(wrapped)
468
+ injectable_class = AsyncCMScopedInjectable
469
+ wrapper = asynccontextmanager(wrapped)
470
+
471
+ elif isgeneratorfunction(wrapped):
472
+ hint = get_yield_hint(wrapped)
473
+ injectable_class = CMScopedInjectable
474
+ wrapper = contextmanager(wrapped)
475
+
476
+ else:
477
+ injectable_class = SimpleScopedInjectable
478
+ hint = wrapper = wrapped # type: ignore[assignment]
479
+
480
+ hints = on if hint is None else (hint, on)
481
+ self.injectable(
482
+ wrapper,
483
+ cls=lambda factory: injectable_class(factory, scope_name),
484
+ ignore_type_hint=True,
485
+ inject=inject,
486
+ on=hints,
487
+ mode=mode,
488
+ )
489
+ return wrapped
490
+
491
+ return decorator
492
+
427
493
  def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
428
494
  def decorator(wp: type[T]) -> type[T]:
429
495
  updater = Updater(
430
- factory=SyncCaller(wp),
431
496
  classes=(wp,),
432
- injectable_factory=lambda _: ShouldBeInjectable(wp),
497
+ injectable=ShouldBeInjectable(wp),
433
498
  mode=Mode.FALLBACK,
434
499
  )
435
500
  self.update(updater)
@@ -449,6 +514,7 @@ class Module(Broker, EventListener):
449
514
  lazy_instance = Lazy(wp)
450
515
  self.injectable(
451
516
  lambda: ~lazy_instance,
517
+ ignore_type_hint=True,
452
518
  inject=False,
453
519
  on=(wp, on),
454
520
  mode=mode,
@@ -465,14 +531,12 @@ class Module(Broker, EventListener):
465
531
  alias: bool = False,
466
532
  mode: Mode | ModeStr = Mode.get_default(),
467
533
  ) -> Self:
468
- if not alias:
469
- cls = type(instance)
470
- on = (cls, on)
471
-
534
+ hints = on if alias else (type(instance), on)
472
535
  self.injectable(
473
536
  lambda: instance,
537
+ ignore_type_hint=True,
474
538
  inject=False,
475
- on=on,
539
+ on=hints,
476
540
  mode=mode,
477
541
  )
478
542
  return self
@@ -670,8 +734,11 @@ class Module(Broker, EventListener):
670
734
  priority: Priority | PriorityStr = Priority.get_default(),
671
735
  ) -> Iterator[None]:
672
736
  self.use(module, priority=priority)
673
- yield
674
- self.stop_using(module)
737
+
738
+ try:
739
+ yield
740
+ finally:
741
+ self.stop_using(module)
675
742
 
676
743
  def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:
677
744
  priority = Priority(priority)
@@ -682,7 +749,6 @@ class Module(Broker, EventListener):
682
749
 
683
750
  return self
684
751
 
685
- @synchronized()
686
752
  def unlock(self) -> Self:
687
753
  for broker in self.__brokers:
688
754
  broker.unlock()
@@ -714,12 +780,14 @@ class Module(Broker, EventListener):
714
780
  self.__check_locking()
715
781
 
716
782
  with self.__channel.dispatch(event):
717
- yield
718
- message = str(event)
719
- self.__debug(message)
783
+ try:
784
+ yield
785
+ finally:
786
+ message = str(event)
787
+ self.__debug(message)
720
788
 
721
789
  def __debug(self, message: object) -> None:
722
- for logger in tuple(self.__loggers):
790
+ for logger in self.__loggers:
723
791
  logger.debug(message)
724
792
 
725
793
  def __check_locking(self) -> None:
@@ -741,10 +809,8 @@ class Module(Broker, EventListener):
741
809
  with suppress(KeyError):
742
810
  return cls.__instances[name]
743
811
 
744
- with synchronized():
745
- instance = cls(name)
746
- cls.__instances[name] = instance
747
-
812
+ instance = cls(name)
813
+ cls.__instances[name] = instance
748
814
  return instance
749
815
 
750
816
  @classmethod
@@ -873,10 +939,8 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
873
939
  with suppress(AttributeError):
874
940
  return self.__signature
875
941
 
876
- with synchronized():
877
- signature = inspect_signature(self.wrapped, eval_str=True)
878
- self.__signature = signature
879
-
942
+ signature = inspect_signature(self.wrapped, eval_str=True)
943
+ self.__signature = signature
880
944
  return signature
881
945
 
882
946
  @property
@@ -921,7 +985,6 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
921
985
  self.__owner = owner
922
986
  return self
923
987
 
924
- @synchronized()
925
988
  def update(self, module: Module) -> Self:
926
989
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
927
990
  return self
@@ -968,9 +1031,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
968
1031
  self.__setup_queue = None
969
1032
 
970
1033
  def __setup(self) -> None:
971
- queue = self.__setup_queue
972
-
973
- if queue is None:
1034
+ if (queue := self.__setup_queue) is None:
974
1035
  return
975
1036
 
976
1037
  while True:
@@ -1037,11 +1098,13 @@ class SyncInjectedFunction[**P, T](InjectedFunction[P, T]):
1037
1098
  return self.__inject_metadata__.call(*args, **kwargs)
1038
1099
 
1039
1100
 
1040
- def _get_caller[**P, T](function: Callable[P, T]) -> Caller[P, T]:
1101
+ def extract_caller[**P, T](
1102
+ function: Callable[P, T] | Callable[P, Awaitable[T]],
1103
+ ) -> Caller[P, T]:
1041
1104
  if iscoroutinefunction(function):
1042
1105
  return AsyncCaller(function)
1043
1106
 
1044
1107
  elif isinstance(function, InjectedFunction):
1045
1108
  return function.__inject_metadata__
1046
1109
 
1047
- return SyncCaller(function)
1110
+ return SyncCaller(function) # type: ignore[arg-type]
@@ -0,0 +1,209 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections import defaultdict
5
+ from collections.abc import AsyncIterator, Iterator, MutableMapping
6
+ from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
7
+ from contextvars import ContextVar
8
+ from dataclasses import dataclass, field
9
+ from types import TracebackType
10
+ from typing import (
11
+ Any,
12
+ AsyncContextManager,
13
+ ContextManager,
14
+ Final,
15
+ NoReturn,
16
+ Protocol,
17
+ Self,
18
+ runtime_checkable,
19
+ )
20
+
21
+ from injection._core.common.key import new_short_key
22
+ from injection.exceptions import (
23
+ ScopeAlreadyDefinedError,
24
+ ScopeError,
25
+ ScopeUndefinedError,
26
+ )
27
+
28
+
29
+ @dataclass(repr=False, slots=True)
30
+ class _ScopeState:
31
+ # Shouldn't be instantiated outside `__SCOPES`.
32
+
33
+ __context_var: ContextVar[Scope] = field(
34
+ default_factory=lambda: ContextVar(f"scope@{new_short_key()}"),
35
+ init=False,
36
+ )
37
+ __references: set[Scope] = field(
38
+ default_factory=set,
39
+ init=False,
40
+ )
41
+ __shared_value: Scope | None = field(
42
+ default=None,
43
+ init=False,
44
+ )
45
+
46
+ @property
47
+ def active_scopes(self) -> Iterator[Scope]:
48
+ yield from self.__references
49
+
50
+ if shared_value := self.__shared_value:
51
+ yield shared_value
52
+
53
+ @contextmanager
54
+ def bind_contextual_scope(self, scope: Scope) -> Iterator[None]:
55
+ self.__references.add(scope)
56
+ token = self.__context_var.set(scope)
57
+
58
+ try:
59
+ yield
60
+ finally:
61
+ self.__context_var.reset(token)
62
+ self.__references.remove(scope)
63
+
64
+ @contextmanager
65
+ def bind_shared_scope(self, scope: Scope) -> Iterator[None]:
66
+ if next(self.active_scopes, None):
67
+ raise ScopeError(
68
+ "A shared scope can't be defined when one or more contextual scopes "
69
+ "are defined on the same name."
70
+ )
71
+
72
+ self.__shared_value = scope
73
+
74
+ try:
75
+ yield
76
+ finally:
77
+ self.__shared_value = None
78
+
79
+ def get_scope(self) -> Scope | None:
80
+ return self.__context_var.get(self.__shared_value)
81
+
82
+
83
+ __SCOPES: Final[defaultdict[str, _ScopeState]] = defaultdict(_ScopeState)
84
+
85
+
86
+ @asynccontextmanager
87
+ async def adefine_scope(name: str, *, shared: bool = False) -> AsyncIterator[None]:
88
+ async with AsyncScope() as scope:
89
+ scope.enter(_bind_scope(name, scope, shared))
90
+ yield
91
+
92
+
93
+ @contextmanager
94
+ def define_scope(name: str, *, shared: bool = False) -> Iterator[None]:
95
+ with SyncScope() as scope:
96
+ scope.enter(_bind_scope(name, scope, shared))
97
+ yield
98
+
99
+
100
+ def get_active_scopes(name: str) -> tuple[Scope, ...]:
101
+ return tuple(__SCOPES[name].active_scopes)
102
+
103
+
104
+ def get_scope(name: str) -> Scope:
105
+ scope = __SCOPES[name].get_scope()
106
+
107
+ if scope is None:
108
+ raise ScopeUndefinedError(
109
+ f"Scope `{name}` isn't defined in the current context."
110
+ )
111
+
112
+ return scope
113
+
114
+
115
+ @contextmanager
116
+ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
117
+ state = __SCOPES[name]
118
+
119
+ if state.get_scope():
120
+ raise ScopeAlreadyDefinedError(
121
+ f"Scope `{name}` is already defined in the current context."
122
+ )
123
+
124
+ strategy = (
125
+ state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
126
+ )
127
+
128
+ try:
129
+ with strategy:
130
+ yield
131
+ finally:
132
+ scope.cache.clear()
133
+
134
+
135
+ @runtime_checkable
136
+ class Scope(Protocol):
137
+ __slots__ = ()
138
+
139
+ cache: MutableMapping[Any, Any]
140
+
141
+ @abstractmethod
142
+ async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
143
+ raise NotImplementedError
144
+
145
+ @abstractmethod
146
+ def enter[T](self, context_manager: ContextManager[T]) -> T:
147
+ raise NotImplementedError
148
+
149
+
150
+ @dataclass(repr=False, frozen=True, slots=True)
151
+ class BaseScope[T](Scope, ABC):
152
+ delegate: T
153
+ cache: MutableMapping[Any, Any] = field(
154
+ default_factory=dict,
155
+ init=False,
156
+ hash=False,
157
+ )
158
+
159
+
160
+ class AsyncScope(BaseScope[AsyncExitStack]):
161
+ __slots__ = ()
162
+
163
+ def __init__(self) -> None:
164
+ super().__init__(delegate=AsyncExitStack())
165
+
166
+ async def __aenter__(self) -> Self:
167
+ await self.delegate.__aenter__()
168
+ return self
169
+
170
+ async def __aexit__(
171
+ self,
172
+ exc_type: type[BaseException] | None,
173
+ exc_value: BaseException | None,
174
+ traceback: TracebackType | None,
175
+ ) -> Any:
176
+ return await self.delegate.__aexit__(exc_type, exc_value, traceback)
177
+
178
+ async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
179
+ return await self.delegate.enter_async_context(context_manager)
180
+
181
+ def enter[T](self, context_manager: ContextManager[T]) -> T:
182
+ return self.delegate.enter_context(context_manager)
183
+
184
+
185
+ class SyncScope(BaseScope[ExitStack]):
186
+ __slots__ = ()
187
+
188
+ def __init__(self) -> None:
189
+ super().__init__(delegate=ExitStack())
190
+
191
+ def __enter__(self) -> Self:
192
+ self.delegate.__enter__()
193
+ return self
194
+
195
+ def __exit__(
196
+ self,
197
+ exc_type: type[BaseException] | None,
198
+ exc_value: BaseException | None,
199
+ traceback: TracebackType | None,
200
+ ) -> Any:
201
+ return self.delegate.__exit__(exc_type, exc_value, traceback)
202
+
203
+ async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
204
+ raise ScopeError(
205
+ "Synchronous scope doesn't support asynchronous context manager."
206
+ )
207
+
208
+ def enter[T](self, context_manager: ContextManager[T]) -> T:
209
+ return self.delegate.enter_context(context_manager)
injection/exceptions.py CHANGED
@@ -7,6 +7,9 @@ __all__ = (
7
7
  "ModuleLockError",
8
8
  "ModuleNotUsedError",
9
9
  "NoInjectable",
10
+ "ScopeAlreadyDefinedError",
11
+ "ScopeError",
12
+ "ScopeUndefinedError",
10
13
  )
11
14
 
12
15
 
@@ -36,4 +39,13 @@ class ModuleLockError(ModuleError): ...
36
39
  class ModuleNotUsedError(KeyError, ModuleError): ...
37
40
 
38
41
 
42
+ class ScopeError(InjectionError): ...
43
+
44
+
45
+ class ScopeUndefinedError(LookupError, ScopeError): ...
46
+
47
+
48
+ class ScopeAlreadyDefinedError(ScopeError): ...
49
+
50
+
39
51
  class HookError(InjectionError): ...
@@ -1,11 +1,9 @@
1
- from collections.abc import Awaitable
2
1
  from types import GenericAlias
3
2
  from typing import Any, TypeAliasType
4
3
 
5
4
  from fastapi import Depends
6
5
 
7
6
  from injection import Module, mod
8
- from injection.exceptions import InjectionError
9
7
 
10
8
  __all__ = ("Inject",)
11
9
 
@@ -13,51 +11,17 @@ __all__ = ("Inject",)
13
11
  def Inject[T]( # noqa: N802
14
12
  cls: type[T] | TypeAliasType | GenericAlias,
15
13
  /,
14
+ default: T = NotImplemented,
16
15
  module: Module | None = None,
17
- *,
18
- scoped: bool = True,
19
16
  ) -> Any:
20
17
  """
21
18
  Declare a FastAPI dependency with `python-injection`.
22
19
  """
23
20
 
24
- dependency: InjectionDependency[T] = InjectionDependency(cls, module or mod())
25
- return Depends(dependency, use_cache=scoped)
21
+ module = module or mod()
22
+ lazy_instance = module.aget_lazy_instance(cls, default)
26
23
 
24
+ async def getter() -> T:
25
+ return await lazy_instance
27
26
 
28
- class InjectionDependency[T]:
29
- __slots__ = ("__class", "__lazy_instance", "__module")
30
-
31
- __class: type[T] | TypeAliasType | GenericAlias
32
- __lazy_instance: Awaitable[T]
33
- __module: Module
34
-
35
- def __init__(
36
- self,
37
- cls: type[T] | TypeAliasType | GenericAlias,
38
- module: Module,
39
- ) -> None:
40
- self.__class = cls
41
- self.__lazy_instance = module.aget_lazy_instance(cls, default=NotImplemented)
42
- self.__module = module
43
-
44
- async def __call__(self) -> T:
45
- instance = await self.__lazy_instance
46
-
47
- if instance is NotImplemented:
48
- raise InjectionError(f"`{self.__class}` is an unknown dependency.")
49
-
50
- return instance
51
-
52
- def __eq__(self, other: Any) -> bool:
53
- if isinstance(other, type(self)):
54
- return self.__key == other.__key
55
-
56
- return NotImplemented
57
-
58
- def __hash__(self) -> int:
59
- return hash(self.__key)
60
-
61
- @property
62
- def __key(self) -> tuple[type[T] | TypeAliasType | GenericAlias, Module]:
63
- return self.__class, self.__module
27
+ return Depends(getter, use_cache=False)
@@ -9,6 +9,7 @@ __all__ = (
9
9
  "should_be_test_injectable",
10
10
  "test_constant",
11
11
  "test_injectable",
12
+ "test_scoped",
12
13
  "test_singleton",
13
14
  )
14
15
 
@@ -18,6 +19,7 @@ set_test_constant = mod(_TEST_PROFILE_NAME).set_constant
18
19
  should_be_test_injectable = mod(_TEST_PROFILE_NAME).should_be_injectable
19
20
  test_constant = mod(_TEST_PROFILE_NAME).constant
20
21
  test_injectable = mod(_TEST_PROFILE_NAME).injectable
22
+ test_scoped = mod(_TEST_PROFILE_NAME).scoped
21
23
  test_singleton = mod(_TEST_PROFILE_NAME).singleton
22
24
 
23
25
 
@@ -1,12 +1,15 @@
1
- from typing import ContextManager
1
+ from typing import ContextManager, Final
2
2
 
3
- import injection as _
3
+ from injection import Module
4
4
 
5
- set_test_constant = _.set_constant
6
- should_be_test_injectable = _.should_be_injectable
7
- test_constant = _.constant
8
- test_injectable = _.injectable
9
- test_singleton = _.singleton
5
+ __MODULE: Final[Module] = ...
6
+
7
+ set_test_constant = __MODULE.set_constant
8
+ should_be_test_injectable = __MODULE.should_be_injectable
9
+ test_constant = __MODULE.constant
10
+ test_injectable = __MODULE.injectable
11
+ test_scoped = __MODULE.scoped
12
+ test_singleton = __MODULE.singleton
10
13
 
11
14
  def load_test_profile(*names: str) -> ContextManager[None]:
12
15
  """
injection/utils.py CHANGED
@@ -42,12 +42,17 @@ def load_modules_with_keywords(
42
42
  """
43
43
  Function to import modules from a Python package if one of the keywords is contained in the Python script.
44
44
  The default keywords are:
45
- - `from injection`
45
+ - `from injection `
46
+ - `from injection.`
46
47
  - `import injection`
47
48
  """
48
49
 
49
50
  if keywords is None:
50
- keywords = f"from {injection_package_name}", f"import {injection_package_name}"
51
+ keywords = (
52
+ f"from {injection_package_name} ",
53
+ f"from {injection_package_name}.",
54
+ f"import {injection_package_name}",
55
+ )
51
56
 
52
57
  b_keywords = tuple(keyword.encode() for keyword in keywords)
53
58
 
@@ -1,31 +1,30 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.12.3
3
+ Version: 0.13.1
4
4
  Summary: Fast and easy dependency injection framework.
5
- Home-page: https://github.com/100nm/python-injection
5
+ Project-URL: Repository, https://github.com/100nm/python-injection
6
+ Author: remimd
6
7
  License: MIT
7
8
  Keywords: dependencies,dependency,inject,injection
8
- Author: remimd
9
- Requires-Python: >=3.12, <4
10
9
  Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Natural Language :: English
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
11
16
  Classifier: Topic :: Software Development :: Libraries
12
17
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
13
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
19
  Classifier: Topic :: Software Development :: Testing
15
- Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Operating System :: OS Independent
19
- Classifier: Intended Audience :: Developers
20
- Classifier: Natural Language :: English
21
20
  Classifier: Typing :: Typed
22
- Project-URL: Repository, https://github.com/100nm/python-injection
21
+ Requires-Python: <4,>=3.12
23
22
  Description-Content-Type: text/markdown
24
23
 
25
24
  # python-injection
26
25
 
27
26
  [![CI](https://github.com/100nm/python-injection/actions/workflows/ci.yml/badge.svg)](https://github.com/100nm/python-injection)
28
- [![PyPI](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection/)
27
+ [![PyPI](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection)
29
28
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
30
29
 
31
30
  Fast and easy dependency injection framework.
@@ -80,9 +79,9 @@ if __name__ == "__main__":
80
79
  ## Resources
81
80
 
82
81
  * [**Basic usage**](https://github.com/100nm/python-injection/tree/prod/documentation/basic-usage.md)
82
+ * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
83
83
  * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
84
84
  * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
85
85
  * [**Utils**](https://github.com/100nm/python-injection/tree/prod/documentation/utils.md)
86
86
  * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
87
- * [**Concrete example**](https://github.com/100nm/python-injection/tree/prod/documentation/example)
88
-
87
+ * [**Concrete example**](https://github.com/100nm/python-injection-example)
@@ -0,0 +1,25 @@
1
+ injection/__init__.py,sha256=X0vIAoN4MDlhR7YIkup1qHgqbOkwZ3PWgdSi7h-udaM,1049
2
+ injection/__init__.pyi,sha256=LXBL7_ihiw6NyU94xWGdXkWwxvzRBoLYBSOv88PV2WM,9640
3
+ injection/exceptions.py,sha256=T__732aXxWWUz0sKc39ySteyolCS5tpqQC0oCnzUF2E,917
4
+ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ injection/utils.py,sha256=SZBzmVAwRL-kilS2eRt9MCeNAL4zeaj_7Rm_2NLBzg0,3175
6
+ injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
7
+ injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
8
+ injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
9
+ injection/_core/injectables.py,sha256=SApYnP6vG3b1qi_KJx6hkJyPoyTmZfooxqPpOsxWSVI,4482
10
+ injection/_core/module.py,sha256=wOoHW_bImPSwoxi3i8_rFXP_HWFJ_S78UuY_5EIilFQ,30595
11
+ injection/_core/scope.py,sha256=TKOoxCmi2FIoXis0irReVNayDTte4KjLrTq8ZDcrOSk,5583
12
+ injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ injection/_core/common/asynchronous.py,sha256=LlYMde_55osS3r8Sc3Fh5lgPp5UWmIfvyBzUeIDmMiM,1698
14
+ injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
15
+ injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
16
+ injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
17
+ injection/_core/common/lazy.py,sha256=ZKx2O9CCFsF9F0SLM4zb7jSLksJUv-FBLCPlWltMN5k,1398
18
+ injection/_core/common/type.py,sha256=QbBBhJp7i1p6gLzWX0TgofvfG7yDH-gHfEQcssVZeHo,2186
19
+ injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ injection/integrations/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
21
+ injection/testing/__init__.py,sha256=ALcKuDYNdslmpgqotZzSWrXAW0kNNFUs9nzfO1ZgIGc,783
22
+ injection/testing/__init__.pyi,sha256=6rv5NOYHEaiKMd82E1IIc8lFlLV9ttY57DLiMqGTXt8,482
23
+ python_injection-0.13.1.dist-info/METADATA,sha256=y4ls59BE2tC-G8V8chV5dMl4um6zcLtYOejDr03Z3jQ,2996
24
+ python_injection-0.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ python_injection-0.13.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,11 +0,0 @@
1
- from collections.abc import Iterator
2
- from contextlib import contextmanager
3
- from threading import RLock
4
-
5
-
6
- @contextmanager
7
- def synchronized() -> Iterator[RLock]:
8
- lock = RLock()
9
-
10
- with lock:
11
- yield lock
@@ -1,24 +0,0 @@
1
- injection/__init__.py,sha256=1MI9kXuQfsqLGQK_5-Zm4JjecqopjGNlhsAW8iihn9M,919
2
- injection/__init__.pyi,sha256=Gwtvo6sJVizqz0pTKNaG-Jz_NW0gSqEsoZF7T6p3V1s,8820
3
- injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
4
- injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- injection/_core/common/asynchronous.py,sha256=Sd7r5y42WSNXRtC4NvYRn5y2Jb6nNapj0aAOvkS4WIU,1726
6
- injection/_core/common/event.py,sha256=kev8WUESAXzMn11EJsbTcICaCvYiD556E6BOLvQ54s4,1243
7
- injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
8
- injection/_core/common/lazy.py,sha256=ZKx2O9CCFsF9F0SLM4zb7jSLksJUv-FBLCPlWltMN5k,1398
9
- injection/_core/common/threading.py,sha256=OXm7L3p8c7O7eSkU-RTR7cobqIGMhuo-7gpDXsWKDNQ,214
10
- injection/_core/common/type.py,sha256=c4QfvbkMfYMlNxqt-vq6QJ83ubMnw6AIVI2Rp-tV1PI,1550
11
- injection/_core/descriptors.py,sha256=y1rFTQdCDIMLVQfuQE8ZkTPlVZKgzvwZ2Y20Si05DwM,662
12
- injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
13
- injection/_core/injectables.py,sha256=rBcrTYRpZ69LkHSGm6bve6TGPwf66XkuK2XVACwcRNc,2427
14
- injection/_core/module.py,sha256=e0FVvhplVRbOGrAT-RCNKIR2pTng5qEAxCjuurm0cEA,28675
15
- injection/exceptions.py,sha256=-5Shs7R5rctQXhpMLfcjiMBCzrtFWxC88qETUIHz57s,692
16
- injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- injection/integrations/fastapi.py,sha256=tI9ohXOm_WucqkS-DZJceIVb_mE9LOY_UpIcr6hfRVM,1668
18
- injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- injection/testing/__init__.py,sha256=Wd9sq43CJCV7GjYFSIaikIf4hJ5lfVNS7GP3iI8a1X8,719
20
- injection/testing/__init__.pyi,sha256=Dyf1LjSj3LrjghXjM2xkMiINg-OPWQMtjCEnnfWzaDY,372
21
- injection/utils.py,sha256=a9y95B08Fr9IgDiBffeQdcawQcvAs5gwBDt9cCYX0uE,3061
22
- python_injection-0.12.3.dist-info/METADATA,sha256=8AyuGaX3P5JfCRxQBcnMrwkssXYeKP52UC64xJ5XE-o,2958
23
- python_injection-0.12.3.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
24
- python_injection-0.12.3.dist-info/RECORD,,