python-injection 0.18.8__py3-none-any.whl → 0.18.10__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.
injection/__init__.pyi CHANGED
@@ -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
  """
injection/_core/module.py CHANGED
@@ -996,7 +996,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
996
996
 
997
997
  def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
998
998
  self.__dependencies = Dependencies.empty()
999
- self.__lock = threading.Lock() if threadsafe else nullcontext()
999
+ self.__lock = threading.RLock() if threadsafe else nullcontext()
1000
1000
  self.__owner = None
1001
1001
  self.__tasks = deque()
1002
1002
  self.__wrapped = wrapped
injection/_core/scope.py CHANGED
@@ -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,26 +200,37 @@ 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):
213
- yield _UserScope(scope)
225
+ stack = ExitStack()
226
+ stack.enter_context(states[name].bind(scope))
227
+
228
+ try:
229
+ yield _UserScope(scope, lock)
230
+
231
+ finally:
232
+ with lock:
233
+ stack.close()
214
234
 
215
235
 
216
236
  @runtime_checkable
@@ -304,6 +324,7 @@ class ScopeFacade(Protocol):
304
324
  @dataclass(repr=False, frozen=True, slots=True)
305
325
  class _UserScope(ScopeFacade):
306
326
  scope: Scope
327
+ lock: ContextManager[Any]
307
328
 
308
329
  def set_slot[T](self, key: SlotKey[T], value: T) -> Self:
309
330
  return self.slot_map({key: value})
@@ -311,9 +332,11 @@ class _UserScope(ScopeFacade):
311
332
  def slot_map(self, mapping: Mapping[SlotKey[Any], Any], /) -> Self:
312
333
  cache = self.scope.cache
313
334
 
314
- for slot_key in mapping:
315
- if slot_key in cache:
316
- raise InjectionError("Slot already set.")
335
+ with self.lock:
336
+ for slot_key in mapping:
337
+ if slot_key in cache:
338
+ raise InjectionError("Slot already set.")
339
+
340
+ cache.update(mapping)
317
341
 
318
- cache.update(mapping)
319
342
  return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.18.8
3
+ Version: 0.18.10
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -1,5 +1,5 @@
1
1
  injection/__init__.py,sha256=7ZRUlO5EEPWO7IlbYHD-8DOX-cg4Np4nYq5fpw-U56o,1259
2
- injection/__init__.pyi,sha256=8UOL1ewTqS6_pyvoCRfdOEHI0kYAZOCHkvqOIwQUOPE,10662
2
+ injection/__init__.pyi,sha256=aV1Ebgb7zdCs6b4K-aVzm0JX6W3x0fGp8vtFmWe2U_o,10718
3
3
  injection/entrypoint.py,sha256=12b0_zHAFxHCerAoJTIHkhqi3mLkgheECYAaCUZv_DU,4751
4
4
  injection/exceptions.py,sha256=v57yMujiq6H_zwwn30A8UYEZX9R9k-bY8FnsdaimPM4,1025
5
5
  injection/loaders.py,sha256=6TjVz9yaQDCVIPIIhjeC6GSXm9rn3oUU2b5KjfOYlsM,6568
@@ -7,8 +7,8 @@ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  injection/_core/descriptors.py,sha256=jH0pyIlPurMmU4yXr-HKS_7BJ-9d0XUvEx4pQre3QeI,704
9
9
  injection/_core/injectables.py,sha256=Rg1nxDkbcpeX4ELohrNVMguPhN36SNQuD0JKfyfL6bI,6192
10
- injection/_core/module.py,sha256=pZ_g_rSn5upHJV1MqF87hkxNfgUAzDc1miV-Qca28hg,32015
11
- injection/_core/scope.py,sha256=OBzVY1mUApryqIZKQtwHz7wiuY13MfouyaHp50DpWeQ,8300
10
+ injection/_core/module.py,sha256=4WqrDti98A5CrIWaHlld1GYYm4K-8XkKltchJoyXUTU,32016
11
+ injection/_core/scope.py,sha256=SEXO1CBJc4EzITi6oMotnNqIrRpLzabjNaBPD18_KHA,8784
12
12
  injection/_core/slots.py,sha256=g9TG6CbqRzCsjg01iPyfRtTTUCJnnJOwcj9mJabH0dc,37
13
13
  injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  injection/_core/common/asynchronous.py,sha256=QeS2Lc4gEBFvTA_snOWfme5mTL4BFZWqZ8EzJwOdVos,1816
@@ -22,7 +22,7 @@ injection/ext/fastapi.py,sha256=layUUer5IWiZX6Mmx1_RCYDLNCtEHtpya5ZL6TTBOkY,968
22
22
  injection/ext/fastapi.pyi,sha256=8OZEUjHFB9n7QXv_dtXdDuXW-r2huQEFsJ03gJOOvwQ,125
23
23
  injection/testing/__init__.py,sha256=bJ7WXBXrw4rHc91AFVFnOwFLWOlpvX9Oh2SnRQ_NESo,919
24
24
  injection/testing/__init__.pyi,sha256=raGsGlxwbz3jkzJwA_5oCIE1emWINjT2UuwzbnqRb-0,577
25
- python_injection-0.18.8.dist-info/METADATA,sha256=LC0vhC-mInR1kvwJ6ul9haTlAhtBKWNRaV6knJP-5z8,3397
26
- python_injection-0.18.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- python_injection-0.18.8.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
28
- python_injection-0.18.8.dist-info/RECORD,,
25
+ python_injection-0.18.10.dist-info/METADATA,sha256=65dTrGn29gCNAUxlwj-Kq10XuQ2xtVLzsv9xdrPaVYQ,3398
26
+ python_injection-0.18.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ python_injection-0.18.10.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
28
+ python_injection-0.18.10.dist-info/RECORD,,