modern-di 0.7.2__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of modern-di might be problematic. Click here for more details.

modern_di/container.py CHANGED
@@ -14,7 +14,15 @@ T_co = typing.TypeVar("T_co", covariant=True)
14
14
 
15
15
 
16
16
  class Container(contextlib.AbstractAsyncContextManager["Container"]):
17
- __slots__ = "scope", "parent_container", "context", "_is_async", "_provider_states", "_overrides"
17
+ __slots__ = (
18
+ "_is_async",
19
+ "_overrides",
20
+ "_provider_states",
21
+ "_use_threading_lock",
22
+ "context",
23
+ "parent_container",
24
+ "scope",
25
+ )
18
26
 
19
27
  def __init__(
20
28
  self,
@@ -22,13 +30,15 @@ class Container(contextlib.AbstractAsyncContextManager["Container"]):
22
30
  scope: enum.IntEnum,
23
31
  parent_container: typing.Optional["Container"] = None,
24
32
  context: dict[str, typing.Any] | None = None,
33
+ use_threading_lock: bool = True,
25
34
  ) -> None:
26
35
  self.scope = scope
27
36
  self.parent_container = parent_container
28
37
  self.context: dict[str, typing.Any] = context or {}
29
38
  self._is_async: bool | None = None
30
39
  self._provider_states: dict[str, ProviderState[typing.Any]] = {}
31
- self._overrides: dict[str, typing.Any] = {}
40
+ self._overrides: dict[str, typing.Any] = parent_container._overrides if parent_container else {} # noqa: SLF001
41
+ self._use_threading_lock = use_threading_lock
32
42
 
33
43
  def _exit(self) -> None:
34
44
  self._is_async = None
@@ -74,17 +84,28 @@ class Container(contextlib.AbstractAsyncContextManager["Container"]):
74
84
  return container
75
85
 
76
86
  def fetch_provider_state(
77
- self, provider_id: str, is_async_resource: bool = False, is_lock_required: bool = False
87
+ self,
88
+ provider_id: str,
89
+ is_async_resource: bool = False,
90
+ use_asyncio_lock: bool = False,
91
+ use_threading_lock: bool = False,
78
92
  ) -> ProviderState[typing.Any]:
79
93
  self._check_entered()
80
94
  if is_async_resource and self._is_async is False:
81
95
  msg = "Resolving async resource in sync container is not allowed"
82
96
  raise RuntimeError(msg)
83
97
 
84
- if provider_id not in self._provider_states:
85
- self._provider_states[provider_id] = ProviderState(is_lock_required=is_lock_required)
86
-
87
- return self._provider_states[provider_id]
98
+ if provider_state := self._provider_states.get(provider_id):
99
+ return provider_state
100
+
101
+ # expected to be thread-safe, because setdefault is atomic
102
+ return self._provider_states.setdefault(
103
+ provider_id,
104
+ ProviderState(
105
+ use_asyncio_lock=use_asyncio_lock,
106
+ use_threading_lock=self._use_threading_lock and use_threading_lock,
107
+ ),
108
+ )
88
109
 
89
110
  def override(self, provider_id: str, override_object: object) -> None:
90
111
  self._overrides[provider_id] = override_object
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import contextlib
3
+ import threading
3
4
  import typing
4
5
 
5
6
 
@@ -7,12 +8,13 @@ T_co = typing.TypeVar("T_co", covariant=True)
7
8
 
8
9
 
9
10
  class ProviderState(typing.Generic[T_co]):
10
- __slots__ = "context_stack", "instance", "provider_lock"
11
+ __slots__ = "asyncio_lock", "context_stack", "instance", "threading_lock"
11
12
 
12
- def __init__(self, is_lock_required: bool) -> None:
13
+ def __init__(self, use_asyncio_lock: bool, use_threading_lock: bool) -> None:
13
14
  self.context_stack: contextlib.AsyncExitStack | contextlib.ExitStack | None = None
14
15
  self.instance: T_co | None = None
15
- self.provider_lock: typing.Final = asyncio.Lock() if is_lock_required else None
16
+ self.asyncio_lock: typing.Final = asyncio.Lock() if use_asyncio_lock else None
17
+ self.threading_lock: typing.Final = threading.Lock() if use_threading_lock else None
16
18
 
17
19
  async def async_tear_down(self) -> None:
18
20
  if self.context_stack is None:
@@ -13,10 +13,10 @@ __all__ = [
13
13
  "AbstractProvider",
14
14
  "ContainerProvider",
15
15
  "ContextAdapter",
16
- "Factory",
17
16
  "Dict",
17
+ "Factory",
18
18
  "List",
19
+ "Resource",
19
20
  "Selector",
20
21
  "Singleton",
21
- "Resource",
22
22
  ]
@@ -39,10 +39,10 @@ class AbstractProvider(typing.Generic[T_co], abc.ABC):
39
39
 
40
40
  class AbstractOverrideProvider(AbstractProvider[T_co], abc.ABC):
41
41
  def override(self, override_object: object, container: Container) -> None:
42
- container.find_container(self.scope).override(self.provider_id, override_object)
42
+ container.override(self.provider_id, override_object)
43
43
 
44
44
  def reset_override(self, container: Container) -> None:
45
- container.find_container(self.scope).reset_override(self.provider_id)
45
+ container.reset_override(self.provider_id)
46
46
 
47
47
 
48
48
  class AbstractCreatorProvider(AbstractOverrideProvider[T_co], abc.ABC):
@@ -58,13 +58,13 @@ class Resource(AbstractCreatorProvider[T_co]):
58
58
  return typing.cast(T_co, override)
59
59
 
60
60
  provider_state = container.fetch_provider_state(
61
- self.provider_id, is_async_resource=self._is_async, is_lock_required=True
61
+ self.provider_id, is_async_resource=self._is_async, use_asyncio_lock=True
62
62
  )
63
63
  if provider_state.instance is not None:
64
64
  return typing.cast(T_co, provider_state.instance)
65
65
 
66
- assert provider_state.provider_lock
67
- await provider_state.provider_lock.acquire()
66
+ if provider_state.asyncio_lock:
67
+ await provider_state.asyncio_lock.acquire()
68
68
 
69
69
  try:
70
70
  if provider_state.instance is not None:
@@ -79,7 +79,8 @@ class Resource(AbstractCreatorProvider[T_co]):
79
79
  provider_state.context_stack = contextlib.ExitStack()
80
80
  provider_state.instance = provider_state.context_stack.enter_context(_intermediate_)
81
81
  finally:
82
- provider_state.provider_lock.release()
82
+ if provider_state.asyncio_lock:
83
+ provider_state.asyncio_lock.release()
83
84
 
84
85
  return typing.cast(T_co, provider_state.instance)
85
86
 
@@ -88,7 +89,9 @@ class Resource(AbstractCreatorProvider[T_co]):
88
89
  if (override := container.fetch_override(self.provider_id)) is not None:
89
90
  return typing.cast(T_co, override)
90
91
 
91
- provider_state = container.fetch_provider_state(self.provider_id)
92
+ provider_state = container.fetch_provider_state(
93
+ self.provider_id, is_async_resource=self._is_async, use_threading_lock=True
94
+ )
92
95
  if provider_state.instance is not None:
93
96
  return typing.cast(T_co, provider_state.instance)
94
97
 
@@ -96,11 +99,21 @@ class Resource(AbstractCreatorProvider[T_co]):
96
99
  msg = "Async resource cannot be resolved synchronously"
97
100
  raise RuntimeError(msg)
98
101
 
99
- _intermediate_ = self._sync_build_creator(container)
102
+ if provider_state.threading_lock:
103
+ provider_state.threading_lock.acquire()
100
104
 
101
- provider_state.context_stack = contextlib.ExitStack()
102
- provider_state.instance = provider_state.context_stack.enter_context(
103
- typing.cast(contextlib.AbstractContextManager[typing.Any], _intermediate_)
104
- )
105
+ try:
106
+ if provider_state.instance is not None:
107
+ return typing.cast(T_co, provider_state.instance)
108
+
109
+ _intermediate_ = self._sync_build_creator(container)
110
+
111
+ provider_state.context_stack = contextlib.ExitStack()
112
+ provider_state.instance = provider_state.context_stack.enter_context(
113
+ typing.cast(contextlib.AbstractContextManager[typing.Any], _intermediate_)
114
+ )
115
+ finally:
116
+ if provider_state.threading_lock:
117
+ provider_state.threading_lock.release()
105
118
 
106
119
  return typing.cast(T_co, provider_state.instance)
@@ -26,12 +26,12 @@ class Singleton(AbstractCreatorProvider[T_co]):
26
26
  if (override := container.fetch_override(self.provider_id)) is not None:
27
27
  return typing.cast(T_co, override)
28
28
 
29
- provider_state = container.fetch_provider_state(self.provider_id, is_lock_required=True)
29
+ provider_state = container.fetch_provider_state(self.provider_id, use_asyncio_lock=True)
30
30
  if provider_state.instance is not None:
31
31
  return typing.cast(T_co, provider_state.instance)
32
32
 
33
- assert provider_state.provider_lock
34
- await provider_state.provider_lock.acquire()
33
+ assert provider_state.asyncio_lock
34
+ await provider_state.asyncio_lock.acquire()
35
35
 
36
36
  try:
37
37
  if provider_state.instance is not None:
@@ -39,7 +39,7 @@ class Singleton(AbstractCreatorProvider[T_co]):
39
39
 
40
40
  provider_state.instance = typing.cast(T_co, await self._async_build_creator(container))
41
41
  finally:
42
- provider_state.provider_lock.release()
42
+ provider_state.asyncio_lock.release()
43
43
 
44
44
  return provider_state.instance
45
45
 
@@ -48,9 +48,20 @@ class Singleton(AbstractCreatorProvider[T_co]):
48
48
  if (override := container.fetch_override(self.provider_id)) is not None:
49
49
  return typing.cast(T_co, override)
50
50
 
51
- provider_state = container.fetch_provider_state(self.provider_id)
51
+ provider_state = container.fetch_provider_state(self.provider_id, use_threading_lock=True)
52
52
  if provider_state.instance is not None:
53
53
  return typing.cast(T_co, provider_state.instance)
54
54
 
55
- provider_state.instance = self._sync_build_creator(container)
55
+ if provider_state.threading_lock:
56
+ provider_state.threading_lock.acquire()
57
+
58
+ try:
59
+ if provider_state.instance is not None:
60
+ return typing.cast(T_co, provider_state.instance)
61
+
62
+ provider_state.instance = self._sync_build_creator(container)
63
+ finally:
64
+ if provider_state.threading_lock:
65
+ provider_state.threading_lock.release()
66
+
56
67
  return typing.cast(T_co, provider_state.instance)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: modern-di
3
- Version: 0.7.2
3
+ Version: 0.9.0
4
4
  Summary: Dependency Injection framework with IOC-container and scopes
5
5
  Project-URL: repository, https://github.com/modern-python/modern-di
6
6
  Project-URL: docs, https://modern-di.readthedocs.io
@@ -36,6 +36,7 @@ It is in development state yet and gives you the following:
36
36
  - Overriding dependencies for tests.
37
37
  - Package with zero dependencies.
38
38
  - Integration with FastAPI and LiteStar
39
+ - Thread-safe and asyncio concurrency safe providers
39
40
 
40
41
  📚 [Documentation](https://modern-di.readthedocs.io)
41
42
 
@@ -1,20 +1,20 @@
1
1
  modern_di/__init__.py,sha256=L01VkzSJiV0d0FPrh1DZ-Wy5mUmoG6X-oLz7xYxtehI,194
2
- modern_di/container.py,sha256=wJ18aOVb-RMFEBafTesZdvMUOSlJ-44qcNFtR0Cak1Q,4351
2
+ modern_di/container.py,sha256=a9NB7P4OQ92seGGztu9SGBChmv79nJ9kHxeF8V2zwpg,4900
3
3
  modern_di/graph.py,sha256=X60wtG3Mqus_5YZNiZlQuXoHODBp7rYl_IHJs7GzSQM,1356
4
- modern_di/provider_state.py,sha256=5Bl_iYEpXjMqoWZJ4op2-axo4Z8nR_vYbLVHL_u5R0c,1206
4
+ modern_di/provider_state.py,sha256=oU08QnMr0yhIZKkz0Pee8_RnWtETDE9ux4JB83qhwTI,1358
5
5
  modern_di/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  modern_di/scope.py,sha256=e6Olc-CF89clbYDNGciy-F8EqJt1Mw2703zfuJaEY94,113
7
- modern_di/providers/__init__.py,sha256=r594L4kWv_XCHVZcsfUIGBtlXCmih6NR1Ifa105XoQ4,649
8
- modern_di/providers/abstract.py,sha256=gi4I340Y5eBg0-_lUFsgv1qA0En7oD0F3j53Gf5TgRY,3161
7
+ modern_di/providers/__init__.py,sha256=tnF38UC-INW1GPPsGlj5tLqGi6fMfHodpi91wHDW-jY,649
8
+ modern_di/providers/abstract.py,sha256=UMj4CRn-JfGZfiveWFTkH7V92h4UXS4eYf8noZqPWGQ,3107
9
9
  modern_di/providers/container_provider.py,sha256=r5IEQXgKtPwvHvbqkbPnmGyDGGCCjokTtdard9Rvt40,548
10
10
  modern_di/providers/context_adapter.py,sha256=_b1x3ToQPWT-9KkDioFhw1W8Q1VXZYUnczfYzMTobVA,760
11
11
  modern_di/providers/dict.py,sha256=nCU9iaqteYHDbILAfhrdnbMgS9_emE4MS7Xn2VoUlPo,858
12
12
  modern_di/providers/factory.py,sha256=NozbrprJlRJPWSNdvKR0kOwPt1Q9i_ZLJTPOfzdDDJo,1359
13
13
  modern_di/providers/injected_factory.py,sha256=wK9GG5_d33BdrpimnR6W-zeviZcCS8qe8ZGTmjYtFf4,1135
14
14
  modern_di/providers/list.py,sha256=3hx34RfBRmqzh-cT5D6wSTDJPkBGMK_ul4n9gQz-o9M,769
15
- modern_di/providers/resource.py,sha256=UwIrX63Crf5iUXu3cp80hw7orl9ahy8frstxAf1Y7wc,4098
15
+ modern_di/providers/resource.py,sha256=CsMekkVISklTqN539XqH4iE80vfc6sQMav5OT8fZvyA,4591
16
16
  modern_di/providers/selector.py,sha256=RQbHD2-Liw-TGqu6UELbfCzXYuqxiO_Mg1tLyF3mKQo,1419
17
- modern_di/providers/singleton.py,sha256=7XBNhVzhV5Rh_F7iWZx8is7i7_PuctQ9thKeqIkjnTs,1999
18
- modern_di-0.7.2.dist-info/METADATA,sha256=bmefLrcGPc-baBH_mToSar0qh2m3zEJBPM3_gkOafC8,5440
19
- modern_di-0.7.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
20
- modern_di-0.7.2.dist-info/RECORD,,
17
+ modern_di/providers/singleton.py,sha256=_hUpCmbHgLAigdhBiu0zypwWwrIGdB6_oZkGfuLxzNE,2372
18
+ modern_di-0.9.0.dist-info/METADATA,sha256=jmn_QkeNOtjU_z1raF5FECSDkaLMhBkTXjb8SEqT7Hc,5493
19
+ modern_di-0.9.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
20
+ modern_di-0.9.0.dist-info/RECORD,,