python-injection 0.18.7__tar.gz → 0.18.9__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 (29) hide show
  1. {python_injection-0.18.7 → python_injection-0.18.9}/PKG-INFO +1 -1
  2. {python_injection-0.18.7 → python_injection-0.18.9}/injection/__init__.pyi +3 -0
  3. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/module.py +5 -1
  4. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/scope.py +38 -17
  5. {python_injection-0.18.7 → python_injection-0.18.9}/injection/entrypoint.py +1 -3
  6. {python_injection-0.18.7 → python_injection-0.18.9}/injection/loaders.py +6 -2
  7. {python_injection-0.18.7 → python_injection-0.18.9}/pyproject.toml +1 -1
  8. {python_injection-0.18.7 → python_injection-0.18.9}/.gitignore +0 -0
  9. {python_injection-0.18.7 → python_injection-0.18.9}/LICENSE +0 -0
  10. {python_injection-0.18.7 → python_injection-0.18.9}/README.md +0 -0
  11. {python_injection-0.18.7 → python_injection-0.18.9}/injection/__init__.py +0 -0
  12. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/__init__.py +0 -0
  13. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/__init__.py +0 -0
  14. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/asynchronous.py +0 -0
  15. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/event.py +0 -0
  16. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/invertible.py +0 -0
  17. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/key.py +0 -0
  18. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/lazy.py +0 -0
  19. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/common/type.py +0 -0
  20. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/descriptors.py +0 -0
  21. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/injectables.py +0 -0
  22. {python_injection-0.18.7 → python_injection-0.18.9}/injection/_core/slots.py +0 -0
  23. {python_injection-0.18.7 → python_injection-0.18.9}/injection/exceptions.py +0 -0
  24. {python_injection-0.18.7 → python_injection-0.18.9}/injection/ext/__init__.py +0 -0
  25. {python_injection-0.18.7 → python_injection-0.18.9}/injection/ext/fastapi.py +0 -0
  26. {python_injection-0.18.7 → python_injection-0.18.9}/injection/ext/fastapi.pyi +0 -0
  27. {python_injection-0.18.7 → python_injection-0.18.9}/injection/py.typed +0 -0
  28. {python_injection-0.18.7 → python_injection-0.18.9}/injection/testing/__init__.py +0 -0
  29. {python_injection-0.18.7 → python_injection-0.18.9}/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.7
3
+ Version: 0.18.9
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -35,12 +35,14 @@ def adefine_scope(
35
35
  name: str,
36
36
  /,
37
37
  kind: ScopeKind | ScopeKindStr = ...,
38
+ threadsafe: bool = ...,
38
39
  ) -> AsyncIterator[Scope]: ...
39
40
  @contextmanager
40
41
  def define_scope(
41
42
  name: str,
42
43
  /,
43
44
  kind: ScopeKind | ScopeKindStr = ...,
45
+ threadsafe: bool = ...,
44
46
  ) -> Iterator[Scope]: ...
45
47
  def mod(name: str = ..., /) -> Module:
46
48
  """
@@ -320,6 +322,7 @@ class Module:
320
322
  module: Module,
321
323
  *,
322
324
  priority: Priority | PriorityStr = ...,
325
+ unlock: bool = ...,
323
326
  ) -> Iterator[Self]:
324
327
  """
325
328
  Context manager or decorator for temporary use of a module.
@@ -788,12 +788,16 @@ class Module(Broker, EventListener):
788
788
  module: Module,
789
789
  *,
790
790
  priority: Priority | PriorityStr = Priority.get_default(),
791
+ unlock: bool = False,
791
792
  ) -> Iterator[Self]:
792
793
  self.use(module, priority=priority)
793
794
 
794
795
  try:
795
796
  yield self
796
797
  finally:
798
+ if unlock:
799
+ self.unlock()
800
+
797
801
  self.stop_using(module)
798
802
 
799
803
  def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:
@@ -992,7 +996,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
992
996
 
993
997
  def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
994
998
  self.__dependencies = Dependencies.empty()
995
- self.__lock = threading.Lock() if threadsafe else nullcontext()
999
+ self.__lock = threading.RLock() if threadsafe else nullcontext()
996
1000
  self.__owner = None
997
1001
  self.__tasks = deque()
998
1002
  self.__wrapped = wrapped
@@ -1,10 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
+ import threading
4
5
  from abc import ABC, abstractmethod
5
6
  from collections import defaultdict
6
7
  from collections.abc import AsyncIterator, Iterator, Mapping, MutableMapping
7
- from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
8
+ from contextlib import (
9
+ AsyncExitStack,
10
+ ExitStack,
11
+ asynccontextmanager,
12
+ contextmanager,
13
+ nullcontext,
14
+ )
8
15
  from contextvars import ContextVar
9
16
  from dataclasses import dataclass, field
10
17
  from enum import StrEnum
@@ -129,9 +136,10 @@ async def adefine_scope(
129
136
  name: str,
130
137
  /,
131
138
  kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
139
+ threadsafe: bool = False,
132
140
  ) -> AsyncIterator[ScopeFacade]:
133
141
  async with AsyncScope() as scope:
134
- with _bind_scope(name, scope, kind) as facade:
142
+ with _bind_scope(name, scope, kind, threadsafe) as facade:
135
143
  yield facade
136
144
 
137
145
 
@@ -140,9 +148,10 @@ def define_scope(
140
148
  name: str,
141
149
  /,
142
150
  kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
151
+ threadsafe: bool = False,
143
152
  ) -> Iterator[ScopeFacade]:
144
153
  with SyncScope() as scope:
145
- with _bind_scope(name, scope, kind) as facade:
154
+ with _bind_scope(name, scope, kind, threadsafe) as facade:
146
155
  yield facade
147
156
 
148
157
 
@@ -191,27 +200,39 @@ def _bind_scope(
191
200
  name: str,
192
201
  scope: Scope,
193
202
  kind: ScopeKind | ScopeKindStr,
203
+ threadsafe: bool,
194
204
  ) -> Iterator[ScopeFacade]:
195
- match ScopeKind(kind):
196
- case ScopeKind.CONTEXTUAL:
197
- is_already_defined = bool(get_scope(name, default=None))
198
- states = __CONTEXTUAL_SCOPES
205
+ lock = threading.RLock() if threadsafe else nullcontext()
199
206
 
200
- case ScopeKind.SHARED:
201
- is_already_defined = bool(get_active_scopes(name))
202
- states = __SHARED_SCOPES
207
+ with lock:
208
+ match ScopeKind(kind):
209
+ case ScopeKind.CONTEXTUAL:
210
+ is_already_defined = bool(get_scope(name, default=None))
211
+ states = __CONTEXTUAL_SCOPES
203
212
 
204
- case _:
205
- raise NotImplementedError
213
+ case ScopeKind.SHARED:
214
+ is_already_defined = bool(get_active_scopes(name))
215
+ states = __SHARED_SCOPES
206
216
 
207
- if is_already_defined:
208
- raise ScopeAlreadyDefinedError(
209
- f"Scope `{name}` is already defined in the current context."
210
- )
217
+ case _:
218
+ raise NotImplementedError
219
+
220
+ if is_already_defined:
221
+ raise ScopeAlreadyDefinedError(
222
+ f"Scope `{name}` is already defined in the current context."
223
+ )
211
224
 
212
- with states[name].bind(scope):
225
+ stack = ExitStack()
226
+ binder = states[name].bind(scope)
227
+ stack.enter_context(binder)
228
+
229
+ try:
213
230
  yield _UserScope(scope)
214
231
 
232
+ finally:
233
+ with lock:
234
+ stack.close()
235
+
215
236
 
216
237
  @runtime_checkable
217
238
  class Scope(Protocol):
@@ -153,9 +153,7 @@ class Entrypoint[**P, T]:
153
153
  setup_method = profile_loader.module.make_injected_function(setup_method)
154
154
 
155
155
  def decorator(function: Callable[P, T]) -> Callable[P, _T]:
156
- if profile_loader.module_subsets:
157
- profile_loader.init()
158
-
156
+ profile_loader.init()
159
157
  self = cls(function, profile_loader)
160
158
  return MethodType(setup_method, self)().function
161
159
 
@@ -145,6 +145,10 @@ class ProfileLoader:
145
145
  module: Module = field(default_factory=mod, kw_only=True)
146
146
  __initialized_modules: set[str] = field(default_factory=set, init=False)
147
147
 
148
+ @property
149
+ def __is_empty(self) -> bool:
150
+ return not self.module_subsets
151
+
148
152
  def init(self) -> Self:
149
153
  self.__init_subsets_for(self.module)
150
154
  return self
@@ -159,12 +163,12 @@ class ProfileLoader:
159
163
  self.module.unlock().stop_using(mod(name))
160
164
 
161
165
  def __init_subsets_for(self, module: Module) -> Module:
162
- if not self.__is_initialized(module):
166
+ if not self.__is_empty and not self.__is_initialized(module):
163
167
  target_modules = tuple(
164
168
  self.__init_subsets_for(mod(name))
165
169
  for name in self.module_subsets.get(module.name, ())
166
170
  )
167
- module.unlock().init_modules(*target_modules)
171
+ module.init_modules(*target_modules)
168
172
  self.__mark_initialized(module)
169
173
 
170
174
  return module
@@ -29,7 +29,7 @@ test = [
29
29
 
30
30
  [project]
31
31
  name = "python-injection"
32
- version = "0.18.7"
32
+ version = "0.18.9"
33
33
  description = "Fast and easy dependency injection framework."
34
34
  license = "MIT"
35
35
  license-files = ["LICENSE"]