python-injection 0.26.3__py3-none-any.whl → 0.26.4__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.
@@ -4,7 +4,7 @@ from contextlib import AbstractAsyncContextManager
4
4
  from dataclasses import dataclass
5
5
  from typing import Any, NoReturn, Protocol, runtime_checkable
6
6
 
7
- AsyncSemaphore: Callable[[int], AbstractAsyncContextManager[Any]]
7
+ AsyncLock: Callable[[], AbstractAsyncContextManager[Any]]
8
8
 
9
9
  try:
10
10
  import anyio
@@ -12,10 +12,10 @@ try:
12
12
  except ImportError: # pragma: no cover
13
13
  import asyncio
14
14
 
15
- AsyncSemaphore = asyncio.Semaphore
15
+ AsyncLock = asyncio.Lock
16
16
 
17
17
  else:
18
- AsyncSemaphore = anyio.Semaphore
18
+ AsyncLock = anyio.Lock
19
19
 
20
20
 
21
21
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from contextlib import AbstractContextManager, nullcontext
2
4
  from os import getenv
3
- from threading import RLock
5
+ from threading import Lock
4
6
  from typing import Any, Final
5
7
 
6
8
  _PYTHON_INJECTION_THREADSAFE: Final[bool] = bool(
@@ -8,6 +10,13 @@ _PYTHON_INJECTION_THREADSAFE: Final[bool] = bool(
8
10
  )
9
11
 
10
12
 
11
- def get_lock(threadsafe: bool | None = None) -> AbstractContextManager[Any]:
13
+ def lock_if_threadsafe(
14
+ lock: AbstractContextManager[Any] | None = None,
15
+ threadsafe: bool | None = None,
16
+ ) -> AbstractContextManager[Any]:
12
17
  threadsafe = _PYTHON_INJECTION_THREADSAFE if threadsafe is None else threadsafe
13
- return RLock() if threadsafe else nullcontext()
18
+
19
+ if not threadsafe:
20
+ return nullcontext()
21
+
22
+ return lock or Lock()
@@ -12,7 +12,7 @@ from typing import (
12
12
  runtime_checkable,
13
13
  )
14
14
 
15
- from injection._core.common.asynchronous import AsyncSemaphore, Caller
15
+ from injection._core.common.asynchronous import AsyncLock, Caller
16
16
  from injection._core.common.type import InputType
17
17
  from injection._core.scope import Scope, get_first_scope, in_scope_cache
18
18
  from injection._core.slots import SlotKey
@@ -51,12 +51,12 @@ class TransientInjectable[T](Injectable[T]):
51
51
 
52
52
 
53
53
  class CacheLogic[T]:
54
- __slots__ = ("__semaphore",)
54
+ __slots__ = ("__lock",)
55
55
 
56
- __semaphore: AbstractAsyncContextManager[Any]
56
+ __lock: AbstractAsyncContextManager[Any]
57
57
 
58
58
  def __init__(self) -> None:
59
- self.__semaphore = AsyncSemaphore(1)
59
+ self.__lock = AsyncLock()
60
60
 
61
61
  async def aget_or_create[K](
62
62
  self,
@@ -64,7 +64,7 @@ class CacheLogic[T]:
64
64
  key: K,
65
65
  factory: Callable[..., Awaitable[T]],
66
66
  ) -> T:
67
- async with self.__semaphore:
67
+ async with self.__lock:
68
68
  with suppress(KeyError):
69
69
  return cache[key]
70
70
 
injection/_core/module.py CHANGED
@@ -35,6 +35,7 @@ from inspect import (
35
35
  )
36
36
  from inspect import signature as inspect_signature
37
37
  from logging import Logger, getLogger
38
+ from threading import RLock
38
39
  from types import MethodType
39
40
  from typing import TYPE_CHECKING, Any, ClassVar, Literal, NamedTuple, Self, overload
40
41
 
@@ -44,7 +45,7 @@ from injection._core.common.asynchronous import Caller, SimpleAwaitable
44
45
  from injection._core.common.event import Event, EventChannel, EventListener
45
46
  from injection._core.common.invertible import Invertible, SimpleInvertible
46
47
  from injection._core.common.lazy import Lazy
47
- from injection._core.common.threading import get_lock
48
+ from injection._core.common.threading import lock_if_threadsafe
48
49
  from injection._core.common.type import (
49
50
  InputType,
50
51
  TypeInfo,
@@ -180,6 +181,11 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
180
181
  init=False,
181
182
  repr=False,
182
183
  )
184
+ __lock: RLock = field(
185
+ default_factory=RLock,
186
+ init=False,
187
+ repr=False,
188
+ )
183
189
  __loggers: list[Logger] = field(
184
190
  default_factory=lambda: [getLogger("python-injection")],
185
191
  init=False,
@@ -350,7 +356,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
350
356
  /,
351
357
  threadsafe: bool | None = None,
352
358
  ) -> InjectMetadata[P, T]:
353
- return InjectMetadata(wrapped, threadsafe).listen(self)
359
+ return InjectMetadata(wrapped, self.__lock, threadsafe).listen(self)
354
360
 
355
361
  if TYPE_CHECKING: # pragma: no cover
356
362
 
@@ -397,7 +403,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
397
403
  *,
398
404
  threadsafe: bool | None = None,
399
405
  ) -> T:
400
- with get_lock(threadsafe):
406
+ with lock_if_threadsafe(self.__lock, threadsafe):
401
407
  injectable = self[cls]
402
408
  return await injectable.aget_instance()
403
409
 
@@ -407,7 +413,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
407
413
  *,
408
414
  threadsafe: bool | None = None,
409
415
  ) -> T:
410
- with get_lock(threadsafe):
416
+ with lock_if_threadsafe(self.__lock, threadsafe):
411
417
  injectable = self[cls]
412
418
  return injectable.get_instance()
413
419
 
@@ -598,11 +604,12 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
598
604
  return self
599
605
 
600
606
  def stop_using(self, module: Module) -> Self:
601
- event = ModuleRemoved(self, module)
607
+ if module in self.__modules:
608
+ event = ModuleRemoved(self, module)
602
609
 
603
- with suppress(KeyError), self.dispatch(event):
604
- self.__modules.pop(module)
605
- module.remove_listener(self)
610
+ with self.dispatch(event):
611
+ self.__modules.pop(module)
612
+ module.remove_listener(self)
606
613
 
607
614
  return self
608
615
 
@@ -833,11 +840,12 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
833
840
  def __init__(
834
841
  self,
835
842
  wrapped: Callable[P, T],
843
+ lock: RLock,
836
844
  /,
837
845
  threadsafe: bool | None = None,
838
846
  ) -> None:
839
847
  self.__dependencies = Dependencies.empty()
840
- self.__lock = get_lock(threadsafe)
848
+ self.__lock = lock_if_threadsafe(lock, threadsafe)
841
849
  self.__owner = None
842
850
  self.__tasks = deque()
843
851
  self.__wrapped = wrapped
injection/_core/scope.py CHANGED
@@ -16,6 +16,7 @@ from contextvars import ContextVar
16
16
  from dataclasses import dataclass, field
17
17
  from enum import StrEnum
18
18
  from functools import partial
19
+ from threading import Lock
19
20
  from types import EllipsisType, TracebackType
20
21
  from typing import (
21
22
  TYPE_CHECKING,
@@ -29,7 +30,7 @@ from typing import (
29
30
  runtime_checkable,
30
31
  )
31
32
 
32
- from injection._core.common.threading import get_lock
33
+ from injection._core.common.threading import lock_if_threadsafe
33
34
  from injection._core.slots import SlotKey
34
35
  from injection.exceptions import (
35
36
  InjectionError,
@@ -38,6 +39,8 @@ from injection.exceptions import (
38
39
  ScopeUndefinedError,
39
40
  )
40
41
 
42
+ __thread_lock: Final[Lock] = Lock()
43
+
41
44
 
42
45
  class ScopeKind(StrEnum):
43
46
  CONTEXTUAL = "contextual"
@@ -197,7 +200,7 @@ def _bind_scope(
197
200
  kind: ScopeKind | ScopeKindStr,
198
201
  threadsafe: bool | None,
199
202
  ) -> Iterator[ScopeFacade]:
200
- lock = get_lock(threadsafe)
203
+ lock = lock_if_threadsafe(__thread_lock, threadsafe)
201
204
 
202
205
  with lock:
203
206
  if get_first_scope(name, default=None):
@@ -209,7 +212,7 @@ def _bind_scope(
209
212
  stack.enter_context(__scope_resolvers[kind][name].bind(scope))
210
213
 
211
214
  try:
212
- yield _UserScope(scope, lock)
215
+ yield _UserScope(scope, lock_if_threadsafe(threadsafe=threadsafe))
213
216
 
214
217
  finally:
215
218
  with lock:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-injection
3
- Version: 0.26.3
3
+ Version: 0.26.4
4
4
  Summary: Dead-simple dependency injection framework for Python.
5
5
  Project-URL: Documentation, https://python-injection.remimd.dev
6
6
  Project-URL: Repository, https://github.com/100nm/python-injection
@@ -32,8 +32,8 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # python-injection
34
34
 
35
- [![PyPI - Version](https://img.shields.io/pypi/v/python-injection.svg?color=546d78&style=for-the-badge)](https://pypi.org/project/python-injection)
36
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/python-injection.svg?color=546d78&style=for-the-badge)](https://pypistats.org/packages/python-injection)
35
+ [![PyPI - Version](https://shieldcn.dev/pypi/v/python-injection.svg?variant=secondary)](https://pypi.org/project/python-injection)
36
+ [![PyPI - Downloads](https://shieldcn.dev/pypi/dm/python-injection.svg?variant=secondary)](https://pypistats.org/packages/python-injection)
37
37
 
38
38
  ## Project motivations
39
39
 
@@ -7,24 +7,24 @@ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  injection/_core/asfunction.py,sha256=GY228WHQWvA-9rg3unR8ttyIJlhwxy0bC3LUO5K8Erg,1864
9
9
  injection/_core/descriptors.py,sha256=OBuGGPPn9b2dLTfFA1tdUD2zv11F4_1h1Vsm_cNaF-M,3602
10
- injection/_core/injectables.py,sha256=6PY_-DCfCd7eTZoHwBxvuPMX78XIYq128sAKOAjoiOQ,6467
10
+ injection/_core/injectables.py,sha256=MXl6sNKWAWvwcY7BnDf8fXhlIxc_DadzNB2RzhO7WA4,6436
11
11
  injection/_core/locator.py,sha256=zSzLN7WiEPlYyrWra8siVnqx78jEW7AZ0rn5hZSmF3o,8270
12
- injection/_core/module.py,sha256=W4HsK7OJ_wlKIkca4g6O4LT5v1nf2D6ow-303JasLQ0,28918
13
- injection/_core/scope.py,sha256=WIkv8PvmSZt4us3PKZA8KuM4Mtd7TzXVhi-V_zxt-kE,9060
12
+ injection/_core/module.py,sha256=Ss562rKUfm9y7sHRknc-2Pa-YX0QD5zvEBDp3AtuAtA,29189
13
+ injection/_core/scope.py,sha256=wFosijGOCyFJ5nbk8sclL47GbiI5gpYHegaJ0HloclU,9196
14
14
  injection/_core/slots.py,sha256=g9TG6CbqRzCsjg01iPyfRtTTUCJnnJOwcj9mJabH0dc,37
15
15
  injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- injection/_core/common/asynchronous.py,sha256=QldcR-QyrDD8u1T-P5gEm2Vb-2DbU64bdE7NZXf_AhU,1785
16
+ injection/_core/common/asynchronous.py,sha256=GFJHzUDoq0TnD3BWQ4669HhQ4kEsZH-B97dFoBzCft0,1757
17
17
  injection/_core/common/event.py,sha256=oZj1Uw4-XAcdBFzy7xmn9CC6HoIMxbdVXkHICLSORqk,1252
18
18
  injection/_core/common/invertible.py,sha256=gA_vw5nBjgp_w9MrDK5jMO8lhuOQWON8BbPpKzEuIY0,502
19
19
  injection/_core/common/lazy.py,sha256=jTv7mYChvRp6WLWILLbEejrHq24gFRKOkkUuZjnwYlE,796
20
- injection/_core/common/threading.py,sha256=-A1z7l363JZx8em88INpvV-DOQ7xO2y57u5I7Pta-Jw,459
20
+ injection/_core/common/threading.py,sha256=m4AQsE_uTLpL8r0XzZ0q6-PZzcTH2izmXQF1Ojm-TVg,592
21
21
  injection/_core/common/type.py,sha256=d1CtWyu--7Ru7vi9uqAQucO1oXFmAsuUhP2j-dCYkfM,1533
22
22
  injection/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  injection/ext/fastapi.py,sha256=wpiNyHKEPwQ7sAJt30XpkgiCi4-gfmyqkQWw9BejXwg,1633
24
24
  injection/ext/fastapi.pyi,sha256=HLs7mfruIEFRrN_Xf8oCvSa4qwHWfwm6HHU_KMedXkE,185
25
25
  injection/testing/__init__.py,sha256=ZWMacZuGhK8Edq1L0Ng0PaRlgKXEunyFZkFvx7-IYIQ,935
26
26
  injection/testing/__init__.pyi,sha256=ZFQMUZmvBVkV6Ch7jiYE9zeJhnqHAQnt3vWQ1Th0zDk,646
27
- python_injection-0.26.3.dist-info/METADATA,sha256=QhVKKiAbtQNT1Y68W0JPBo4nTU_yPPCMY-sAiEqgjRw,3706
28
- python_injection-0.26.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
29
- python_injection-0.26.3.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
30
- python_injection-0.26.3.dist-info/RECORD,,
27
+ python_injection-0.26.4.dist-info/METADATA,sha256=Ih2Ar0OxlZABkHCW6poo9PzQTBB7dBrnJS6QlCGmnck,3672
28
+ python_injection-0.26.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
29
+ python_injection-0.26.4.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
30
+ python_injection-0.26.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any