python-injection 0.14.6.post0__py3-none-any.whl → 0.14.6.post1__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/_core/scope.py CHANGED
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import itertools
3
4
  from abc import ABC, abstractmethod
4
5
  from collections import defaultdict
5
- from collections.abc import AsyncIterator, Iterator, MutableMapping
6
+ from collections.abc import AsyncIterator, Iterator, Mapping, MutableMapping
6
7
  from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
7
8
  from contextvars import ContextVar
8
9
  from dataclasses import dataclass, field
9
- from types import TracebackType
10
+ from types import EllipsisType, TracebackType
10
11
  from typing import (
11
12
  Any,
12
13
  AsyncContextManager,
@@ -15,6 +16,7 @@ from typing import (
15
16
  NoReturn,
16
17
  Protocol,
17
18
  Self,
19
+ overload,
18
20
  runtime_checkable,
19
21
  )
20
22
 
@@ -26,18 +28,32 @@ from injection.exceptions import (
26
28
  )
27
29
 
28
30
 
29
- @dataclass(repr=False, slots=True)
30
- class _ScopeState:
31
- # Shouldn't be instantiated outside `__SCOPES`.
31
+ @runtime_checkable
32
+ class ScopeState(Protocol):
33
+ __slots__ = ()
34
+
35
+ @property
36
+ @abstractmethod
37
+ def active_scopes(self) -> Iterator[Scope]:
38
+ raise NotImplementedError
39
+
40
+ @abstractmethod
41
+ def bind(self, scope: Scope) -> ContextManager[None]:
42
+ raise NotImplementedError
43
+
44
+ @abstractmethod
45
+ def get_scope(self) -> Scope | None:
46
+ raise NotImplementedError
47
+
48
+
49
+ @dataclass(repr=False, frozen=True, slots=True)
50
+ class _ContextualScopeState(ScopeState):
51
+ # Shouldn't be instantiated outside `__CONTEXTUAL_SCOPES`.
32
52
 
33
53
  __context_var: ContextVar[Scope] = field(
34
54
  default_factory=lambda: ContextVar(f"scope@{new_short_key()}"),
35
55
  init=False,
36
56
  )
37
- __default: Scope | None = field(
38
- default=None,
39
- init=False,
40
- )
41
57
  __references: set[Scope] = field(
42
58
  default_factory=set,
43
59
  init=False,
@@ -45,13 +61,10 @@ class _ScopeState:
45
61
 
46
62
  @property
47
63
  def active_scopes(self) -> Iterator[Scope]:
48
- yield from self.__references
49
-
50
- if default := self.__default:
51
- yield default
64
+ return iter(self.__references)
52
65
 
53
66
  @contextmanager
54
- def bind_contextual_scope(self, scope: Scope) -> Iterator[None]:
67
+ def bind(self, scope: Scope) -> Iterator[None]:
55
68
  self.__references.add(scope)
56
69
  token = self.__context_var.set(scope)
57
70
 
@@ -61,26 +74,38 @@ class _ScopeState:
61
74
  self.__context_var.reset(token)
62
75
  self.__references.remove(scope)
63
76
 
64
- @contextmanager
65
- def bind_shared_scope(self, scope: Scope) -> Iterator[None]:
66
- if next(self.active_scopes, None):
67
- raise ScopeError(
68
- "A shared scope can't be defined when one or more contextual scopes "
69
- "are defined on the same name."
70
- )
77
+ def get_scope(self) -> Scope | None:
78
+ return self.__context_var.get(None)
79
+
80
+
81
+ @dataclass(repr=False, slots=True)
82
+ class _SharedScopeState(ScopeState):
83
+ __scope: Scope | None = field(default=None)
84
+
85
+ @property
86
+ def active_scopes(self) -> Iterator[Scope]:
87
+ if scope := self.__scope:
88
+ yield scope
71
89
 
72
- self.__default = scope
90
+ @contextmanager
91
+ def bind(self, scope: Scope) -> Iterator[None]:
92
+ self.__scope = scope
73
93
 
74
94
  try:
75
95
  yield
76
96
  finally:
77
- self.__default = None
97
+ self.__scope = None
78
98
 
79
99
  def get_scope(self) -> Scope | None:
80
- return self.__context_var.get(self.__default)
100
+ return self.__scope
81
101
 
82
102
 
83
- __SCOPES: Final[defaultdict[str, _ScopeState]] = defaultdict(_ScopeState)
103
+ __CONTEXTUAL_SCOPES: Final[Mapping[str, ScopeState]] = defaultdict(
104
+ _ContextualScopeState,
105
+ )
106
+ __SHARED_SCOPES: Final[Mapping[str, ScopeState]] = defaultdict(
107
+ _SharedScopeState,
108
+ )
84
109
 
85
110
 
86
111
  @asynccontextmanager
@@ -98,36 +123,52 @@ def define_scope(name: str, *, shared: bool = False) -> Iterator[None]:
98
123
 
99
124
 
100
125
  def get_active_scopes(name: str) -> tuple[Scope, ...]:
101
- state = __SCOPES.get(name)
126
+ active_scopes = (
127
+ state.active_scopes
128
+ for states in (__CONTEXTUAL_SCOPES, __SHARED_SCOPES)
129
+ if (state := states.get(name))
130
+ )
131
+ return tuple(itertools.chain.from_iterable(active_scopes))
132
+
102
133
 
103
- if state is None:
104
- return ()
134
+ @overload
135
+ def get_scope(name: str, default: EllipsisType = ...) -> Scope: ...
105
136
 
106
- return tuple(state.active_scopes)
107
137
 
138
+ @overload
139
+ def get_scope[T](name: str, default: T) -> Scope | T: ...
108
140
 
109
- def get_scope(name: str) -> Scope:
110
- state = __SCOPES.get(name)
111
141
 
112
- if state is None or (scope := state.get_scope()) is None:
142
+ def get_scope(name, default=...): # type: ignore[no-untyped-def]
143
+ for states in (__CONTEXTUAL_SCOPES, __SHARED_SCOPES):
144
+ state = states.get(name)
145
+ if state and (scope := state.get_scope()):
146
+ return scope
147
+
148
+ if default is Ellipsis:
113
149
  raise ScopeUndefinedError(
114
150
  f"Scope `{name}` isn't defined in the current context."
115
151
  )
116
152
 
117
- return scope
153
+ return default
118
154
 
119
155
 
120
156
  @contextmanager
121
157
  def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
122
- state = __SCOPES[name]
158
+ if shared:
159
+ is_already_defined = bool(get_active_scopes(name))
160
+ state = __SHARED_SCOPES[name]
161
+
162
+ else:
163
+ is_already_defined = bool(get_scope(name, default=None))
164
+ state = __CONTEXTUAL_SCOPES[name]
123
165
 
124
- if state.get_scope():
166
+ if is_already_defined:
125
167
  raise ScopeAlreadyDefinedError(
126
168
  f"Scope `{name}` is already defined in the current context."
127
169
  )
128
170
 
129
- strategy = state.bind_shared_scope if shared else state.bind_contextual_scope
130
- with strategy(scope):
171
+ with state.bind(scope):
131
172
  yield
132
173
 
133
174
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.14.6.post0
3
+ Version: 0.14.6.post1
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -7,7 +7,7 @@ injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
8
8
  injection/_core/injectables.py,sha256=idNkQZZ29vd73G_lE-eS5C7zGeVe_ALNkUt8M6YjZrk,5519
9
9
  injection/_core/module.py,sha256=DLw0pD3HDXfNhzbWM0yeCDKg-Mwg8JAzqZq43tFAXik,31814
10
- injection/_core/scope.py,sha256=SnjfYnZ62BkxEUh3wXKHl7ivCHRrPFiTa5GMxC-8ACM,5533
10
+ injection/_core/scope.py,sha256=fZ6zvuP9RO_9wZvMcM13_elSoztMaYKen1MgTP3s8t4,6555
11
11
  injection/_core/slots.py,sha256=6LoG0XtaRnIGDSG8s-FfUIw_50gL0bl4X3Fo_n-hdak,680
12
12
  injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  injection/_core/common/asynchronous.py,sha256=QeS2Lc4gEBFvTA_snOWfme5mTL4BFZWqZ8EzJwOdVos,1816
@@ -20,6 +20,6 @@ injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
20
20
  injection/integrations/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
21
21
  injection/testing/__init__.py,sha256=SiImXDd0-DO1a8S5nbUQRtgDX8iaU_nHcp8DdqwtD2M,896
22
22
  injection/testing/__init__.pyi,sha256=iOii0i9F5n7znltGeGQYI2KXC_if9SAogLh1h03yx-0,540
23
- python_injection-0.14.6.post0.dist-info/METADATA,sha256=sRxRmH0mUX84dvnm-e2Vw6dyZrSOqNqaoVdn5A82U_4,3205
24
- python_injection-0.14.6.post0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- python_injection-0.14.6.post0.dist-info/RECORD,,
23
+ python_injection-0.14.6.post1.dist-info/METADATA,sha256=g-UJxN15zRFae7U8t5Fv2KU-naXLksipOjwefxrmBhs,3205
24
+ python_injection-0.14.6.post1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ python_injection-0.14.6.post1.dist-info/RECORD,,