python-injection 0.12.3__py3-none-any.whl → 0.13.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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,,