python-injection 0.18.9__tar.gz → 0.18.11__tar.gz

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.
Files changed (30) hide show
  1. {python_injection-0.18.9 → python_injection-0.18.11}/PKG-INFO +1 -1
  2. {python_injection-0.18.9 → python_injection-0.18.11}/injection/__init__.pyi +29 -17
  3. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/asynchronous.py +1 -0
  4. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/lazy.py +1 -15
  5. python_injection-0.18.11/injection/_core/common/threading.py +7 -0
  6. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/module.py +61 -42
  7. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/scope.py +12 -16
  8. {python_injection-0.18.9 → python_injection-0.18.11}/injection/ext/fastapi.py +11 -3
  9. {python_injection-0.18.9 → python_injection-0.18.11}/injection/ext/fastapi.pyi +2 -0
  10. {python_injection-0.18.9 → python_injection-0.18.11}/pyproject.toml +1 -1
  11. {python_injection-0.18.9 → python_injection-0.18.11}/.gitignore +0 -0
  12. {python_injection-0.18.9 → python_injection-0.18.11}/LICENSE +0 -0
  13. {python_injection-0.18.9 → python_injection-0.18.11}/README.md +0 -0
  14. {python_injection-0.18.9 → python_injection-0.18.11}/injection/__init__.py +0 -0
  15. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/__init__.py +0 -0
  16. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/__init__.py +0 -0
  17. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/event.py +0 -0
  18. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/invertible.py +0 -0
  19. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/key.py +0 -0
  20. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/common/type.py +0 -0
  21. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/descriptors.py +0 -0
  22. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/injectables.py +0 -0
  23. {python_injection-0.18.9 → python_injection-0.18.11}/injection/_core/slots.py +0 -0
  24. {python_injection-0.18.9 → python_injection-0.18.11}/injection/entrypoint.py +0 -0
  25. {python_injection-0.18.9 → python_injection-0.18.11}/injection/exceptions.py +0 -0
  26. {python_injection-0.18.9 → python_injection-0.18.11}/injection/ext/__init__.py +0 -0
  27. {python_injection-0.18.9 → python_injection-0.18.11}/injection/loaders.py +0 -0
  28. {python_injection-0.18.9 → python_injection-0.18.11}/injection/py.typed +0 -0
  29. {python_injection-0.18.9 → python_injection-0.18.11}/injection/testing/__init__.py +0 -0
  30. {python_injection-0.18.9 → python_injection-0.18.11}/injection/testing/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.18.9
3
+ Version: 0.18.11
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -217,8 +217,13 @@ class Module:
217
217
  /,
218
218
  threadsafe: bool = ...,
219
219
  ) -> Callable[..., Awaitable[T]]: ...
220
- async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
221
- def find_instance[T](self, cls: _InputType[T]) -> T:
220
+ async def afind_instance[T](
221
+ self,
222
+ cls: _InputType[T],
223
+ *,
224
+ threadsafe: bool = ...,
225
+ ) -> T: ...
226
+ def find_instance[T](self, cls: _InputType[T], *, threadsafe: bool = ...) -> T:
222
227
  """
223
228
  Function used to retrieve an instance associated with the type passed in
224
229
  parameter or an exception will be raised.
@@ -229,59 +234,66 @@ class Module:
229
234
  self,
230
235
  cls: _InputType[T],
231
236
  default: Default,
237
+ *,
238
+ threadsafe: bool = ...,
232
239
  ) -> T | Default: ...
233
240
  @overload
234
241
  async def aget_instance[T](
235
242
  self,
236
243
  cls: _InputType[T],
237
- default: None = ...,
238
- ) -> T | None: ...
244
+ default: T = ...,
245
+ *,
246
+ threadsafe: bool = ...,
247
+ ) -> T: ...
239
248
  @overload
240
249
  def get_instance[T, Default](
241
250
  self,
242
251
  cls: _InputType[T],
243
252
  default: Default,
253
+ *,
254
+ threadsafe: bool = ...,
244
255
  ) -> T | Default:
245
256
  """
246
257
  Function used to retrieve an instance associated with the type passed in
247
- parameter or return `None`.
258
+ parameter or return `NotImplemented`.
248
259
  """
249
260
 
250
261
  @overload
251
262
  def get_instance[T](
252
263
  self,
253
264
  cls: _InputType[T],
254
- default: None = ...,
255
- ) -> T | None: ...
265
+ default: T = ...,
266
+ *,
267
+ threadsafe: bool = ...,
268
+ ) -> T: ...
256
269
  @overload
257
270
  def aget_lazy_instance[T, Default](
258
271
  self,
259
272
  cls: _InputType[T],
260
273
  default: Default,
261
274
  *,
262
- cache: bool = ...,
275
+ threadsafe: bool = ...,
263
276
  ) -> Awaitable[T | Default]: ...
264
277
  @overload
265
278
  def aget_lazy_instance[T](
266
279
  self,
267
280
  cls: _InputType[T],
268
- default: None = ...,
281
+ default: T = ...,
269
282
  *,
270
- cache: bool = ...,
271
- ) -> Awaitable[T | None]: ...
283
+ threadsafe: bool = ...,
284
+ ) -> Awaitable[T]: ...
272
285
  @overload
273
286
  def get_lazy_instance[T, Default](
274
287
  self,
275
288
  cls: _InputType[T],
276
289
  default: Default,
277
290
  *,
278
- cache: bool = ...,
291
+ threadsafe: bool = ...,
279
292
  ) -> _Invertible[T | Default]:
280
293
  """
281
294
  Function used to retrieve an instance associated with the type passed in
282
- parameter or `None`. Return a `Invertible` object. To access the instance
295
+ parameter or `NotImplemented`. Return a `Invertible` object. To access the instance
283
296
  contained in an invertible object, simply use a wavy line (~).
284
- With `cache=True`, the instance retrieved will always be the same.
285
297
 
286
298
  Example: instance = ~lazy_instance
287
299
  """
@@ -290,10 +302,10 @@ class Module:
290
302
  def get_lazy_instance[T](
291
303
  self,
292
304
  cls: _InputType[T],
293
- default: None = ...,
305
+ default: T = ...,
294
306
  *,
295
- cache: bool = ...,
296
- ) -> _Invertible[T | None]: ...
307
+ threadsafe: bool = ...,
308
+ ) -> _Invertible[T]: ...
297
309
  def init_modules(self, *modules: Module) -> Self:
298
310
  """
299
311
  Function to clean modules in use and to use those passed as parameters.
@@ -52,6 +52,7 @@ try:
52
52
 
53
53
  def create_semaphore(value: int) -> AsyncContextManager[Any]:
54
54
  return anyio.Semaphore(value)
55
+
55
56
  except ImportError: # pragma: no cover
56
57
  import asyncio
57
58
 
@@ -1,7 +1,6 @@
1
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
1
+ from collections.abc import Callable, Iterator
2
2
  from functools import partial
3
3
 
4
- from injection._core.common.asynchronous import SimpleAwaitable
5
4
  from injection._core.common.invertible import Invertible, SimpleInvertible
6
5
 
7
6
 
@@ -18,19 +17,6 @@ def lazy[T](factory: Callable[..., T]) -> Invertible[T]:
18
17
  return SimpleInvertible(getter)
19
18
 
20
19
 
21
- def alazy[T](factory: Callable[..., Awaitable[T]]) -> Awaitable[T]:
22
- async def cache() -> AsyncIterator[T]:
23
- nonlocal factory
24
- value = await factory()
25
- del factory
26
-
27
- while True:
28
- yield value
29
-
30
- getter = partial(anext, cache())
31
- return SimpleAwaitable(getter)
32
-
33
-
34
20
  class Lazy[T](Invertible[T]):
35
21
  __slots__ = ("__invertible", "__is_set")
36
22
 
@@ -0,0 +1,7 @@
1
+ from contextlib import nullcontext
2
+ from threading import RLock
3
+ from typing import Any, ContextManager
4
+
5
+
6
+ def get_lock(threadsafe: bool) -> ContextManager[Any]:
7
+ return RLock() if threadsafe else nullcontext()
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import threading
4
3
  from abc import ABC, abstractmethod
5
4
  from collections import OrderedDict, deque
6
5
  from collections.abc import (
@@ -14,7 +13,7 @@ from collections.abc import (
14
13
  Iterator,
15
14
  Mapping,
16
15
  )
17
- from contextlib import asynccontextmanager, contextmanager, nullcontext, suppress
16
+ from contextlib import asynccontextmanager, contextmanager, suppress
18
17
  from dataclasses import dataclass, field
19
18
  from enum import StrEnum
20
19
  from functools import partial, partialmethod, singledispatchmethod, update_wrapper
@@ -51,7 +50,8 @@ from injection._core.common.asynchronous import (
51
50
  from injection._core.common.event import Event, EventChannel, EventListener
52
51
  from injection._core.common.invertible import Invertible, SimpleInvertible
53
52
  from injection._core.common.key import new_short_key
54
- from injection._core.common.lazy import Lazy, alazy, lazy
53
+ from injection._core.common.lazy import Lazy, lazy
54
+ from injection._core.common.threading import get_lock
55
55
  from injection._core.common.type import (
56
56
  InputType,
57
57
  TypeInfo,
@@ -617,35 +617,48 @@ class Module(Broker, EventListener):
617
617
  )
618
618
  return factory.__inject_metadata__.acall
619
619
 
620
- async def afind_instance[T](self, cls: InputType[T]) -> T:
621
- injectable = self[cls]
622
- return await injectable.aget_instance()
620
+ async def afind_instance[T](
621
+ self,
622
+ cls: InputType[T],
623
+ *,
624
+ threadsafe: bool = False,
625
+ ) -> T:
626
+ with get_lock(threadsafe):
627
+ injectable = self[cls]
628
+ return await injectable.aget_instance()
623
629
 
624
- def find_instance[T](self, cls: InputType[T]) -> T:
625
- injectable = self[cls]
626
- return injectable.get_instance()
630
+ def find_instance[T](self, cls: InputType[T], *, threadsafe: bool = False) -> T:
631
+ with get_lock(threadsafe):
632
+ injectable = self[cls]
633
+ return injectable.get_instance()
627
634
 
628
635
  @overload
629
636
  async def aget_instance[T, Default](
630
637
  self,
631
638
  cls: InputType[T],
632
639
  default: Default,
640
+ *,
641
+ threadsafe: bool = ...,
633
642
  ) -> T | Default: ...
634
643
 
635
644
  @overload
636
645
  async def aget_instance[T](
637
646
  self,
638
647
  cls: InputType[T],
639
- default: None = ...,
640
- ) -> T | None: ...
648
+ default: T = ...,
649
+ *,
650
+ threadsafe: bool = ...,
651
+ ) -> T: ...
641
652
 
642
653
  async def aget_instance[T, Default](
643
654
  self,
644
655
  cls: InputType[T],
645
- default: Default | None = None,
646
- ) -> T | Default | None:
656
+ default: Default = NotImplemented,
657
+ *,
658
+ threadsafe: bool = False,
659
+ ) -> T | Default:
647
660
  try:
648
- return await self.afind_instance(cls)
661
+ return await self.afind_instance(cls, threadsafe=threadsafe)
649
662
  except (KeyError, SkipInjectable):
650
663
  return default
651
664
 
@@ -654,22 +667,28 @@ class Module(Broker, EventListener):
654
667
  self,
655
668
  cls: InputType[T],
656
669
  default: Default,
670
+ *,
671
+ threadsafe: bool = ...,
657
672
  ) -> T | Default: ...
658
673
 
659
674
  @overload
660
675
  def get_instance[T](
661
676
  self,
662
677
  cls: InputType[T],
663
- default: None = ...,
664
- ) -> T | None: ...
678
+ default: T = ...,
679
+ *,
680
+ threadsafe: bool = ...,
681
+ ) -> T: ...
665
682
 
666
683
  def get_instance[T, Default](
667
684
  self,
668
685
  cls: InputType[T],
669
- default: Default | None = None,
670
- ) -> T | Default | None:
686
+ default: Default = NotImplemented,
687
+ *,
688
+ threadsafe: bool = False,
689
+ ) -> T | Default:
671
690
  try:
672
- return self.find_instance(cls)
691
+ return self.find_instance(cls, threadsafe=threadsafe)
673
692
  except (KeyError, SkipInjectable):
674
693
  return default
675
694
 
@@ -679,29 +698,29 @@ class Module(Broker, EventListener):
679
698
  cls: InputType[T],
680
699
  default: Default,
681
700
  *,
682
- cache: bool = ...,
701
+ threadsafe: bool = ...,
683
702
  ) -> Awaitable[T | Default]: ...
684
703
 
685
704
  @overload
686
705
  def aget_lazy_instance[T](
687
706
  self,
688
707
  cls: InputType[T],
689
- default: None = ...,
708
+ default: T = ...,
690
709
  *,
691
- cache: bool = ...,
692
- ) -> Awaitable[T | None]: ...
710
+ threadsafe: bool = ...,
711
+ ) -> Awaitable[T]: ...
693
712
 
694
713
  def aget_lazy_instance[T, Default](
695
714
  self,
696
715
  cls: InputType[T],
697
- default: Default | None = None,
716
+ default: Default = NotImplemented,
698
717
  *,
699
- cache: bool = False,
700
- ) -> Awaitable[T | Default | None]:
701
- if cache:
702
- return alazy(lambda: self.aget_instance(cls, default))
703
-
704
- function = self.make_injected_function(lambda instance=default: instance)
718
+ threadsafe: bool = False,
719
+ ) -> Awaitable[T | Default]:
720
+ function = self.make_injected_function(
721
+ lambda instance=default: instance,
722
+ threadsafe=threadsafe,
723
+ )
705
724
  metadata = function.__inject_metadata__.set_owner(cls)
706
725
  return SimpleAwaitable(metadata.acall)
707
726
 
@@ -711,29 +730,29 @@ class Module(Broker, EventListener):
711
730
  cls: InputType[T],
712
731
  default: Default,
713
732
  *,
714
- cache: bool = ...,
733
+ threadsafe: bool = ...,
715
734
  ) -> Invertible[T | Default]: ...
716
735
 
717
736
  @overload
718
737
  def get_lazy_instance[T](
719
738
  self,
720
739
  cls: InputType[T],
721
- default: None = ...,
740
+ default: T = ...,
722
741
  *,
723
- cache: bool = ...,
724
- ) -> Invertible[T | None]: ...
742
+ threadsafe: bool = ...,
743
+ ) -> Invertible[T]: ...
725
744
 
726
745
  def get_lazy_instance[T, Default](
727
746
  self,
728
747
  cls: InputType[T],
729
- default: Default | None = None,
748
+ default: Default = NotImplemented,
730
749
  *,
731
- cache: bool = False,
732
- ) -> Invertible[T | Default | None]:
733
- if cache:
734
- return lazy(lambda: self.get_instance(cls, default))
735
-
736
- function = self.make_injected_function(lambda instance=default: instance)
750
+ threadsafe: bool = False,
751
+ ) -> Invertible[T | Default]:
752
+ function = self.make_injected_function(
753
+ lambda instance=default: instance,
754
+ threadsafe=threadsafe,
755
+ )
737
756
  metadata = function.__inject_metadata__.set_owner(cls)
738
757
  return SimpleInvertible(metadata.call)
739
758
 
@@ -996,7 +1015,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
996
1015
 
997
1016
  def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
998
1017
  self.__dependencies = Dependencies.empty()
999
- self.__lock = threading.RLock() if threadsafe else nullcontext()
1018
+ self.__lock = get_lock(threadsafe)
1000
1019
  self.__owner = None
1001
1020
  self.__tasks = deque()
1002
1021
  self.__wrapped = wrapped
@@ -1,17 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
- import threading
5
4
  from abc import ABC, abstractmethod
6
5
  from collections import defaultdict
7
6
  from collections.abc import AsyncIterator, Iterator, Mapping, MutableMapping
8
- from contextlib import (
9
- AsyncExitStack,
10
- ExitStack,
11
- asynccontextmanager,
12
- contextmanager,
13
- nullcontext,
14
- )
7
+ from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
15
8
  from contextvars import ContextVar
16
9
  from dataclasses import dataclass, field
17
10
  from enum import StrEnum
@@ -30,6 +23,7 @@ from typing import (
30
23
  )
31
24
 
32
25
  from injection._core.common.key import new_short_key
26
+ from injection._core.common.threading import get_lock
33
27
  from injection._core.slots import SlotKey
34
28
  from injection.exceptions import (
35
29
  InjectionError,
@@ -202,7 +196,7 @@ def _bind_scope(
202
196
  kind: ScopeKind | ScopeKindStr,
203
197
  threadsafe: bool,
204
198
  ) -> Iterator[ScopeFacade]:
205
- lock = threading.RLock() if threadsafe else nullcontext()
199
+ lock = get_lock(threadsafe)
206
200
 
207
201
  with lock:
208
202
  match ScopeKind(kind):
@@ -223,11 +217,10 @@ def _bind_scope(
223
217
  )
224
218
 
225
219
  stack = ExitStack()
226
- binder = states[name].bind(scope)
227
- stack.enter_context(binder)
220
+ stack.enter_context(states[name].bind(scope))
228
221
 
229
222
  try:
230
- yield _UserScope(scope)
223
+ yield _UserScope(scope, lock)
231
224
 
232
225
  finally:
233
226
  with lock:
@@ -325,6 +318,7 @@ class ScopeFacade(Protocol):
325
318
  @dataclass(repr=False, frozen=True, slots=True)
326
319
  class _UserScope(ScopeFacade):
327
320
  scope: Scope
321
+ lock: ContextManager[Any]
328
322
 
329
323
  def set_slot[T](self, key: SlotKey[T], value: T) -> Self:
330
324
  return self.slot_map({key: value})
@@ -332,9 +326,11 @@ class _UserScope(ScopeFacade):
332
326
  def slot_map(self, mapping: Mapping[SlotKey[Any], Any], /) -> Self:
333
327
  cache = self.scope.cache
334
328
 
335
- for slot_key in mapping:
336
- if slot_key in cache:
337
- raise InjectionError("Slot already set.")
329
+ with self.lock:
330
+ for slot_key in mapping:
331
+ if slot_key in cache:
332
+ raise InjectionError("Slot already set.")
333
+
334
+ cache.update(mapping)
338
335
 
339
- cache.update(mapping)
340
336
  return self
@@ -1,3 +1,4 @@
1
+ from dataclasses import dataclass, field
1
2
  from types import GenericAlias
2
3
  from typing import Annotated, Any, TypeAliasType
3
4
 
@@ -5,20 +6,26 @@ from fastapi import Depends
5
6
 
6
7
  from injection import Module, mod
7
8
 
8
- __all__ = ("Inject",)
9
+ __all__ = ("Inject", "InjectThreadSafe")
9
10
 
10
11
 
12
+ @dataclass(eq=False, frozen=True, slots=True)
11
13
  class FastAPIInject:
12
- __slots__ = ()
14
+ module: Module = field(default_factory=mod)
15
+ threadsafe: bool = field(default=False)
13
16
 
14
17
  def __call__[T](
15
18
  self,
16
19
  cls: type[T] | TypeAliasType | GenericAlias,
17
20
  /,
18
21
  default: T = NotImplemented,
22
+ *,
19
23
  module: Module | None = None,
24
+ threadsafe: bool | None = None,
20
25
  ) -> Any:
21
- ainstance = (module or mod()).aget_lazy_instance(cls, default)
26
+ module = module or self.module
27
+ threadsafe = self.threadsafe if threadsafe is None else threadsafe
28
+ ainstance = module.aget_lazy_instance(cls, default, threadsafe=threadsafe)
22
29
 
23
30
  async def dependency() -> T:
24
31
  return await ainstance
@@ -34,5 +41,6 @@ class FastAPIInject:
34
41
 
35
42
 
36
43
  Inject = FastAPIInject()
44
+ InjectThreadSafe = FastAPIInject(threadsafe=True)
37
45
 
38
46
  del FastAPIInject
@@ -3,3 +3,5 @@ from typing import Annotated
3
3
  from fastapi import Depends
4
4
 
5
5
  type Inject[T, *Metadata] = Annotated[T, Depends(...), *Metadata]
6
+
7
+ type InjectThreadSafe[T, *Metadata] = Inject[T, *Metadata]
@@ -29,7 +29,7 @@ test = [
29
29
 
30
30
  [project]
31
31
  name = "python-injection"
32
- version = "0.18.9"
32
+ version = "0.18.11"
33
33
  description = "Fast and easy dependency injection framework."
34
34
  license = "MIT"
35
35
  license-files = ["LICENSE"]