python-injection 0.14.6.post0__tar.gz → 0.14.6.post2__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.
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/PKG-INFO +1 -1
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/invertible.py +2 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/module.py +2 -2
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/scope.py +78 -37
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/pyproject.toml +1 -1
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/.gitignore +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/README.md +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/__init__.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/__init__.pyi +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/__init__.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/asynchronous.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/event.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/key.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/type.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/descriptors.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/injectables.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/slots.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/exceptions.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/integrations/__init__.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/integrations/fastapi.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/py.typed +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/testing/__init__.py +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/testing/__init__.pyi +0 -0
- {python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/utils.py +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import threading
|
3
4
|
from abc import ABC, abstractmethod
|
4
5
|
from collections import OrderedDict, deque
|
5
6
|
from collections.abc import (
|
@@ -27,7 +28,6 @@ from inspect import (
|
|
27
28
|
)
|
28
29
|
from inspect import signature as inspect_signature
|
29
30
|
from logging import Logger, getLogger
|
30
|
-
from threading import Lock
|
31
31
|
from types import MethodType
|
32
32
|
from typing import (
|
33
33
|
Any,
|
@@ -982,7 +982,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
982
982
|
|
983
983
|
def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
|
984
984
|
self.__dependencies = Dependencies.empty()
|
985
|
-
self.__lock = Lock() if threadsafe else nullcontext()
|
985
|
+
self.__lock = threading.Lock() if threadsafe else nullcontext()
|
986
986
|
self.__owner = None
|
987
987
|
self.__tasks = deque()
|
988
988
|
self.__wrapped = wrapped
|
@@ -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
|
-
@
|
30
|
-
class
|
31
|
-
|
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
|
-
|
49
|
-
|
50
|
-
if default := self.__default:
|
51
|
-
yield default
|
64
|
+
return iter(self.__references)
|
52
65
|
|
53
66
|
@contextmanager
|
54
|
-
def
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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.
|
97
|
+
self.__scope = None
|
78
98
|
|
79
99
|
def get_scope(self) -> Scope | None:
|
80
|
-
return self.
|
100
|
+
return self.__scope
|
81
101
|
|
82
102
|
|
83
|
-
|
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
|
-
|
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
|
-
|
104
|
-
|
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
|
-
|
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
|
153
|
+
return default
|
118
154
|
|
119
155
|
|
120
156
|
@contextmanager
|
121
157
|
def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
|
122
|
-
|
158
|
+
if shared:
|
159
|
+
is_already_defined = bool(get_active_scopes(name))
|
160
|
+
states = __SHARED_SCOPES
|
161
|
+
|
162
|
+
else:
|
163
|
+
is_already_defined = bool(get_scope(name, default=None))
|
164
|
+
states = __CONTEXTUAL_SCOPES
|
123
165
|
|
124
|
-
if
|
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
|
-
|
130
|
-
with strategy(scope):
|
171
|
+
with states[name].bind(scope):
|
131
172
|
yield
|
132
173
|
|
133
174
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/__init__.py
RENAMED
File without changes
|
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/event.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/key.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/lazy.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/common/type.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/descriptors.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/_core/injectables.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/integrations/__init__.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/integrations/fastapi.py
RENAMED
File without changes
|
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/testing/__init__.py
RENAMED
File without changes
|
{python_injection-0.14.6.post0 → python_injection-0.14.6.post2}/injection/testing/__init__.pyi
RENAMED
File without changes
|
File without changes
|