modern-di 0.7.1__tar.gz → 0.8.0__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.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: modern-di
3
- Version: 0.7.1
3
+ Version: 0.8.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
@@ -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
+ "scope",
19
+ "parent_container",
20
+ "context",
21
+ "_is_async",
22
+ "_provider_states",
23
+ "_overrides",
24
+ "_use_threading_lock",
25
+ )
18
26
 
19
27
  def __init__(
20
28
  self,
@@ -22,6 +30,7 @@ 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
@@ -29,6 +38,7 @@ class Container(contextlib.AbstractAsyncContextManager["Container"]):
29
38
  self._is_async: bool | None = None
30
39
  self._provider_states: dict[str, ProviderState[typing.Any]] = {}
31
40
  self._overrides: dict[str, typing.Any] = {}
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__ = "context_stack", "instance", "asyncio_lock", "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:
@@ -9,16 +9,21 @@ T_co = typing.TypeVar("T_co", covariant=True)
9
9
  P = typing.ParamSpec("P")
10
10
 
11
11
 
12
- class InjectedFactory(typing.Generic[T_co]):
13
- __slots__ = ("_factory_provider",)
12
+ class InjectedFactory(AbstractProvider[T_co]):
13
+ __slots__ = [*AbstractProvider.BASE_SLOTS, "_factory_provider"]
14
14
 
15
15
  def __init__(self, factory_provider: AbstractProvider[T_co]) -> None:
16
+ super().__init__(factory_provider.scope)
16
17
  self._factory_provider = factory_provider
17
18
 
18
- async def async_resolve(self, container: Container) -> typing.Callable[[], T_co]:
19
+ async def async_resolve(self, container: Container) -> typing.Callable[[], T_co]: # type: ignore[override]
19
20
  await self._factory_provider.async_resolve(container)
20
21
  return functools.partial(self._factory_provider.sync_resolve, container)
21
22
 
22
- def sync_resolve(self, container: Container) -> typing.Callable[[], T_co]:
23
+ def sync_resolve(self, container: Container) -> typing.Callable[[], T_co]: # type: ignore[override]
23
24
  self._factory_provider.sync_resolve(container)
24
25
  return functools.partial(self._factory_provider.sync_resolve, container)
26
+
27
+ @property
28
+ def cast(self) -> typing.Callable[[], T_co]: # type: ignore[override]
29
+ return typing.cast(typing.Callable[[], T_co], self)
@@ -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)
@@ -24,6 +24,7 @@ dev = [
24
24
  "pytest",
25
25
  "pytest-cov",
26
26
  "pytest-asyncio",
27
+ "pytest-repeat",
27
28
  "ruff",
28
29
  "mypy",
29
30
  "typing-extensions",
File without changes
File without changes
File without changes
File without changes