python-injection 0.24.0__tar.gz → 0.25.1__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.24.0 → python_injection-0.25.1}/PKG-INFO +1 -1
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/__init__.pyi +3 -2
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/asfunction.py +1 -1
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/asynchronous.py +14 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/type.py +1 -2
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/injectables.py +5 -4
- python_injection-0.25.1/injection/_core/locator.py +284 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/module.py +104 -299
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/scope.py +4 -4
- python_injection-0.25.1/injection/testing/__init__.py +30 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/testing/__init__.pyi +2 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/pyproject.toml +1 -1
- python_injection-0.24.0/injection/testing/__init__.py +0 -29
- {python_injection-0.24.0 → python_injection-0.25.1}/.gitignore +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/LICENSE +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/README.md +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/__init__.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/__init__.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/event.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/invertible.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/key.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/common/threading.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/descriptors.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/_core/slots.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/entrypoint.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/exceptions.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/ext/__init__.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/ext/fastapi.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/ext/fastapi.pyi +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/loaders.py +0 -0
- {python_injection-0.24.0 → python_injection-0.25.1}/injection/py.typed +0 -0
|
@@ -9,8 +9,9 @@ from ._core.asfunction import AsFunctionWrappedType as _AsFunctionWrappedType
|
|
|
9
9
|
from ._core.common.invertible import Invertible as _Invertible
|
|
10
10
|
from ._core.common.type import InputType as _InputType
|
|
11
11
|
from ._core.common.type import TypeInfo as _TypeInfo
|
|
12
|
-
from ._core.
|
|
13
|
-
from ._core.
|
|
12
|
+
from ._core.locator import InjectableFactory as _InjectableFactory
|
|
13
|
+
from ._core.locator import ModeStr
|
|
14
|
+
from ._core.module import PriorityStr
|
|
14
15
|
from ._core.scope import ScopeKindStr
|
|
15
16
|
|
|
16
17
|
type _Decorator[T] = Callable[[T], T]
|
|
@@ -64,3 +64,17 @@ class SyncCaller[**P, T](Caller[P, T]):
|
|
|
64
64
|
|
|
65
65
|
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
66
66
|
return self.callable(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@runtime_checkable
|
|
70
|
+
class HiddenCaller[**P, T](Protocol):
|
|
71
|
+
__slots__ = ()
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def __injection_hidden_caller__(self) -> Caller[P, T]:
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
80
|
+
raise NotImplementedError
|
|
@@ -19,8 +19,7 @@ from typing import (
|
|
|
19
19
|
get_type_hints,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
type
|
|
23
|
-
type InputType[T] = TypeDef[T] | UnionType
|
|
22
|
+
type InputType[T] = type[T] | TypeAliasType | GenericAlias | UnionType
|
|
24
23
|
type TypeInfo[T] = (
|
|
25
24
|
InputType[T]
|
|
26
25
|
| Callable[..., T]
|
|
@@ -122,11 +122,12 @@ class SingletonInjectable[T](Injectable[T]):
|
|
|
122
122
|
class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
123
123
|
factory: Caller[..., R]
|
|
124
124
|
scope_name: str
|
|
125
|
+
key: SlotKey[T] = field(default_factory=SlotKey)
|
|
125
126
|
logic: CacheLogic[T] = field(default_factory=CacheLogic)
|
|
126
127
|
|
|
127
128
|
@property
|
|
128
129
|
def is_locked(self) -> bool:
|
|
129
|
-
return in_scope_cache(self, self.scope_name)
|
|
130
|
+
return in_scope_cache(self.key, self.scope_name)
|
|
130
131
|
|
|
131
132
|
@abstractmethod
|
|
132
133
|
async def abuild(self, scope: Scope) -> T:
|
|
@@ -139,12 +140,12 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
|
139
140
|
async def aget_instance(self) -> T:
|
|
140
141
|
scope = self.__get_scope()
|
|
141
142
|
factory = partial(self.abuild, scope)
|
|
142
|
-
return await self.logic.aget_or_create(scope.cache, self, factory)
|
|
143
|
+
return await self.logic.aget_or_create(scope.cache, self.key, factory)
|
|
143
144
|
|
|
144
145
|
def get_instance(self) -> T:
|
|
145
146
|
scope = self.__get_scope()
|
|
146
147
|
factory = partial(self.build, scope)
|
|
147
|
-
return self.logic.get_or_create(scope.cache, self, factory)
|
|
148
|
+
return self.logic.get_or_create(scope.cache, self.key, factory)
|
|
148
149
|
|
|
149
150
|
def unlock(self) -> None:
|
|
150
151
|
if self.is_locked:
|
|
@@ -187,7 +188,7 @@ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
|
|
187
188
|
return self.factory.call()
|
|
188
189
|
|
|
189
190
|
def unlock(self) -> None:
|
|
190
|
-
remove_scoped_values(self, self.scope_name)
|
|
191
|
+
remove_scoped_values(self.key, self.scope_name)
|
|
191
192
|
|
|
192
193
|
|
|
193
194
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Awaitable, Callable, Collection, Iterable, Iterator
|
|
5
|
+
from contextlib import suppress
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
from inspect import iscoroutinefunction
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
ContextManager,
|
|
12
|
+
Literal,
|
|
13
|
+
NamedTuple,
|
|
14
|
+
Protocol,
|
|
15
|
+
Self,
|
|
16
|
+
runtime_checkable,
|
|
17
|
+
)
|
|
18
|
+
from weakref import WeakKeyDictionary
|
|
19
|
+
|
|
20
|
+
from injection._core.common.asynchronous import (
|
|
21
|
+
AsyncCaller,
|
|
22
|
+
Caller,
|
|
23
|
+
HiddenCaller,
|
|
24
|
+
SyncCaller,
|
|
25
|
+
)
|
|
26
|
+
from injection._core.common.event import Event, EventChannel, EventListener
|
|
27
|
+
from injection._core.common.type import InputType
|
|
28
|
+
from injection._core.injectables import Injectable
|
|
29
|
+
from injection.exceptions import NoInjectable, SkipInjectable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class LocatorEvent(Event, ABC):
|
|
34
|
+
locator: Locator
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class LocatorDependenciesUpdated[T](LocatorEvent):
|
|
39
|
+
classes: Collection[InputType[T]]
|
|
40
|
+
mode: Mode
|
|
41
|
+
|
|
42
|
+
def __str__(self) -> str:
|
|
43
|
+
length = len(self.classes)
|
|
44
|
+
formatted_types = ", ".join(f"`{cls}`" for cls in self.classes)
|
|
45
|
+
return (
|
|
46
|
+
f"{length} dependenc{'ies' if length > 1 else 'y'} have been "
|
|
47
|
+
f"updated{f': {formatted_types}' if formatted_types else ''}."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
type InjectableFactory[T] = Callable[[Caller[..., T]], Injectable[T]]
|
|
52
|
+
|
|
53
|
+
type Recipe[**P, T] = Callable[P, T] | Callable[P, Awaitable[T]]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InjectionProvider(ABC):
|
|
57
|
+
__slots__ = ("__weakref__",)
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def make_injected_function[**P, T](
|
|
61
|
+
self,
|
|
62
|
+
wrapped: Callable[P, T],
|
|
63
|
+
/,
|
|
64
|
+
) -> Callable[P, T]:
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@runtime_checkable
|
|
69
|
+
class InjectableBroker[T](Protocol):
|
|
70
|
+
__slots__ = ()
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def get(self, provider: InjectionProvider) -> Injectable[T] | None:
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def request(self, provider: InjectionProvider) -> Injectable[T]:
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
82
|
+
class DynamicInjectableBroker[T](InjectableBroker[T]):
|
|
83
|
+
injectable_factory: InjectableFactory[T]
|
|
84
|
+
recipe: Recipe[..., T]
|
|
85
|
+
cache: WeakKeyDictionary[InjectionProvider, Injectable[T]] = field(
|
|
86
|
+
default_factory=WeakKeyDictionary,
|
|
87
|
+
init=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get(self, provider: InjectionProvider) -> Injectable[T] | None:
|
|
91
|
+
return self.cache.get(provider)
|
|
92
|
+
|
|
93
|
+
def request(self, provider: InjectionProvider) -> Injectable[T]:
|
|
94
|
+
with suppress(KeyError):
|
|
95
|
+
return self.cache[provider]
|
|
96
|
+
|
|
97
|
+
injectable = _make_injectable(
|
|
98
|
+
self.injectable_factory,
|
|
99
|
+
provider.make_injected_function(self.recipe), # type: ignore[misc]
|
|
100
|
+
)
|
|
101
|
+
self.cache[provider] = injectable
|
|
102
|
+
return injectable
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
106
|
+
class StaticInjectableBroker[T](InjectableBroker[T]):
|
|
107
|
+
value: Injectable[T]
|
|
108
|
+
|
|
109
|
+
def get(self, provider: InjectionProvider) -> Injectable[T] | None:
|
|
110
|
+
return self.value
|
|
111
|
+
|
|
112
|
+
def request(self, provider: InjectionProvider) -> Injectable[T]:
|
|
113
|
+
return self.value
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def from_factory(
|
|
117
|
+
cls,
|
|
118
|
+
injectable_factory: InjectableFactory[T],
|
|
119
|
+
recipe: Recipe[..., T],
|
|
120
|
+
) -> Self:
|
|
121
|
+
return cls(_make_injectable(injectable_factory, recipe))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Mode(StrEnum):
|
|
125
|
+
FALLBACK = "fallback"
|
|
126
|
+
NORMAL = "normal"
|
|
127
|
+
OVERRIDE = "override"
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def rank(self) -> int:
|
|
131
|
+
return tuple(type(self)).index(self)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def get_default(cls) -> Mode:
|
|
135
|
+
return cls.NORMAL
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
type ModeStr = Literal["fallback", "normal", "override"]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Record[T](NamedTuple):
|
|
142
|
+
broker: InjectableBroker[T]
|
|
143
|
+
mode: Mode
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(repr=False, eq=False, frozen=True, kw_only=True, slots=True)
|
|
147
|
+
class Updater[T]:
|
|
148
|
+
classes: Collection[InputType[T]]
|
|
149
|
+
broker: InjectableBroker[T]
|
|
150
|
+
mode: Mode
|
|
151
|
+
|
|
152
|
+
def make_record(self) -> Record[T]:
|
|
153
|
+
return Record(self.broker, self.mode)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
157
|
+
class Locator:
|
|
158
|
+
__records: dict[InputType[Any], Record[Any]] = field(
|
|
159
|
+
default_factory=dict,
|
|
160
|
+
init=False,
|
|
161
|
+
)
|
|
162
|
+
__channel: EventChannel = field(
|
|
163
|
+
default_factory=EventChannel,
|
|
164
|
+
init=False,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
168
|
+
return cls in self.__records
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def __brokers(self) -> frozenset[InjectableBroker[Any]]:
|
|
172
|
+
return frozenset(record.broker for record in self.__records.values())
|
|
173
|
+
|
|
174
|
+
def is_locked(self, provider: InjectionProvider) -> bool:
|
|
175
|
+
return any(
|
|
176
|
+
injectable.is_locked for injectable in self.__iter_injectables(provider)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def request[T](
|
|
180
|
+
self,
|
|
181
|
+
cls: InputType[T],
|
|
182
|
+
/,
|
|
183
|
+
provider: InjectionProvider,
|
|
184
|
+
) -> Injectable[T]:
|
|
185
|
+
try:
|
|
186
|
+
record = self.__records[cls]
|
|
187
|
+
except KeyError as exc:
|
|
188
|
+
raise NoInjectable(cls) from exc
|
|
189
|
+
else:
|
|
190
|
+
return record.broker.request(provider)
|
|
191
|
+
|
|
192
|
+
def update[T](self, updater: Updater[T]) -> Self:
|
|
193
|
+
record = updater.make_record()
|
|
194
|
+
records = dict(self.__prepare_for_updating(updater.classes, record))
|
|
195
|
+
|
|
196
|
+
if records:
|
|
197
|
+
event = LocatorDependenciesUpdated(self, records.keys(), record.mode)
|
|
198
|
+
|
|
199
|
+
with self.dispatch(event):
|
|
200
|
+
self.__records.update(records)
|
|
201
|
+
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def unlock(self, provider: InjectionProvider) -> None:
|
|
205
|
+
for injectable in self.__iter_injectables(provider):
|
|
206
|
+
injectable.unlock()
|
|
207
|
+
|
|
208
|
+
async def all_ready(self, provider: InjectionProvider) -> None:
|
|
209
|
+
for injectable in self.__iter_injectables(provider):
|
|
210
|
+
if injectable.is_locked:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
with suppress(SkipInjectable):
|
|
214
|
+
await injectable.aget_instance()
|
|
215
|
+
|
|
216
|
+
def add_listener(self, listener: EventListener) -> Self:
|
|
217
|
+
self.__channel.add_listener(listener)
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def dispatch(self, event: Event) -> ContextManager[None]:
|
|
221
|
+
return self.__channel.dispatch(event)
|
|
222
|
+
|
|
223
|
+
def __iter_injectables(
|
|
224
|
+
self,
|
|
225
|
+
provider: InjectionProvider,
|
|
226
|
+
) -> Iterator[Injectable[Any]]:
|
|
227
|
+
for broker in self.__brokers:
|
|
228
|
+
injectable = broker.get(provider)
|
|
229
|
+
|
|
230
|
+
if injectable is None:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
yield injectable
|
|
234
|
+
|
|
235
|
+
def __prepare_for_updating[T](
|
|
236
|
+
self,
|
|
237
|
+
classes: Iterable[InputType[T]],
|
|
238
|
+
record: Record[T],
|
|
239
|
+
) -> Iterator[tuple[InputType[T], Record[T]]]:
|
|
240
|
+
for cls in classes:
|
|
241
|
+
try:
|
|
242
|
+
existing = self.__records[cls]
|
|
243
|
+
except KeyError:
|
|
244
|
+
...
|
|
245
|
+
else:
|
|
246
|
+
if not self.__keep_new_record(record, existing, cls):
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
yield cls, record
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def __keep_new_record[T](
|
|
253
|
+
new: Record[T],
|
|
254
|
+
existing: Record[T],
|
|
255
|
+
cls: InputType[T],
|
|
256
|
+
) -> bool:
|
|
257
|
+
new_mode, existing_mode = new.mode, existing.mode
|
|
258
|
+
|
|
259
|
+
if new_mode == Mode.OVERRIDE:
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
elif new_mode == existing_mode:
|
|
263
|
+
raise RuntimeError(f"An injectable already exists for the class `{cls}`.")
|
|
264
|
+
|
|
265
|
+
return new_mode.rank > existing_mode.rank
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _extract_caller[**P, T](
|
|
269
|
+
function: Callable[P, T] | Callable[P, Awaitable[T]],
|
|
270
|
+
) -> Caller[P, T]:
|
|
271
|
+
if iscoroutinefunction(function):
|
|
272
|
+
return AsyncCaller(function)
|
|
273
|
+
|
|
274
|
+
elif isinstance(function, HiddenCaller):
|
|
275
|
+
return function.__injection_hidden_caller__
|
|
276
|
+
|
|
277
|
+
return SyncCaller(function) # type: ignore[arg-type]
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _make_injectable[T](
|
|
281
|
+
injectable_factory: InjectableFactory[T],
|
|
282
|
+
recipe: Recipe[..., T],
|
|
283
|
+
) -> Injectable[T]:
|
|
284
|
+
return injectable_factory(_extract_caller(recipe))
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from abc import ABC
|
|
4
|
+
from abc import ABC
|
|
5
5
|
from collections import OrderedDict, deque
|
|
6
6
|
from collections.abc import (
|
|
7
7
|
AsyncGenerator,
|
|
8
8
|
AsyncIterator,
|
|
9
9
|
Awaitable,
|
|
10
10
|
Callable,
|
|
11
|
-
Collection,
|
|
12
11
|
Generator,
|
|
13
12
|
Iterable,
|
|
14
13
|
Iterator,
|
|
@@ -37,19 +36,16 @@ from typing import (
|
|
|
37
36
|
ContextManager,
|
|
38
37
|
Literal,
|
|
39
38
|
NamedTuple,
|
|
40
|
-
Protocol,
|
|
41
39
|
Self,
|
|
42
40
|
overload,
|
|
43
|
-
runtime_checkable,
|
|
44
41
|
)
|
|
45
42
|
|
|
46
|
-
from type_analyzer import MatchingTypesConfig, iter_matching_types
|
|
43
|
+
from type_analyzer import MatchingTypesConfig, iter_matching_types, matching_types
|
|
47
44
|
|
|
48
45
|
from injection._core.common.asynchronous import (
|
|
49
|
-
AsyncCaller,
|
|
50
46
|
Caller,
|
|
47
|
+
HiddenCaller,
|
|
51
48
|
SimpleAwaitable,
|
|
52
|
-
SyncCaller,
|
|
53
49
|
)
|
|
54
50
|
from injection._core.common.event import Event, EventChannel, EventListener
|
|
55
51
|
from injection._core.common.invertible import Invertible, SimpleInvertible
|
|
@@ -58,7 +54,6 @@ from injection._core.common.lazy import Lazy, alazy, lazy
|
|
|
58
54
|
from injection._core.common.threading import get_lock
|
|
59
55
|
from injection._core.common.type import (
|
|
60
56
|
InputType,
|
|
61
|
-
TypeDef,
|
|
62
57
|
TypeInfo,
|
|
63
58
|
get_return_types,
|
|
64
59
|
get_yield_hint,
|
|
@@ -74,6 +69,18 @@ from injection._core.injectables import (
|
|
|
74
69
|
SingletonInjectable,
|
|
75
70
|
TransientInjectable,
|
|
76
71
|
)
|
|
72
|
+
from injection._core.locator import (
|
|
73
|
+
DynamicInjectableBroker,
|
|
74
|
+
InjectableBroker,
|
|
75
|
+
InjectableFactory,
|
|
76
|
+
InjectionProvider,
|
|
77
|
+
Locator,
|
|
78
|
+
Mode,
|
|
79
|
+
ModeStr,
|
|
80
|
+
Recipe,
|
|
81
|
+
StaticInjectableBroker,
|
|
82
|
+
Updater,
|
|
83
|
+
)
|
|
77
84
|
from injection._core.slots import SlotKey
|
|
78
85
|
from injection.exceptions import (
|
|
79
86
|
ModuleError,
|
|
@@ -88,25 +95,6 @@ Events
|
|
|
88
95
|
"""
|
|
89
96
|
|
|
90
97
|
|
|
91
|
-
@dataclass(frozen=True, slots=True)
|
|
92
|
-
class LocatorEvent(Event, ABC):
|
|
93
|
-
locator: Locator
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@dataclass(frozen=True, slots=True)
|
|
97
|
-
class LocatorDependenciesUpdated[T](LocatorEvent):
|
|
98
|
-
classes: Collection[InputType[T]]
|
|
99
|
-
mode: Mode
|
|
100
|
-
|
|
101
|
-
def __str__(self) -> str:
|
|
102
|
-
length = len(self.classes)
|
|
103
|
-
formatted_types = ", ".join(f"`{cls}`" for cls in self.classes)
|
|
104
|
-
return (
|
|
105
|
-
f"{length} dependenc{'ies' if length > 1 else 'y'} have been "
|
|
106
|
-
f"updated{f': {formatted_types}' if formatted_types else ''}."
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
98
|
@dataclass(frozen=True, slots=True)
|
|
111
99
|
class ModuleEvent(Event, ABC):
|
|
112
100
|
module: Module
|
|
@@ -120,15 +108,17 @@ class ModuleEventProxy(ModuleEvent):
|
|
|
120
108
|
return f"`{self.module}` has propagated an event: {self.origin}"
|
|
121
109
|
|
|
122
110
|
@property
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
yield self.event
|
|
111
|
+
def origin(self) -> Event:
|
|
112
|
+
reversed_proxy_history = reversed(tuple(self.proxy_history))
|
|
113
|
+
return next(reversed_proxy_history, self).event
|
|
128
114
|
|
|
129
115
|
@property
|
|
130
|
-
def
|
|
131
|
-
|
|
116
|
+
def proxy_history(self) -> Iterator[ModuleEventProxy]:
|
|
117
|
+
event = self.event
|
|
118
|
+
|
|
119
|
+
if isinstance(event, ModuleEventProxy):
|
|
120
|
+
yield event
|
|
121
|
+
yield from event.proxy_history
|
|
132
122
|
|
|
133
123
|
|
|
134
124
|
@dataclass(frozen=True, slots=True)
|
|
@@ -160,211 +150,6 @@ class ModulePriorityUpdated(ModuleEvent):
|
|
|
160
150
|
)
|
|
161
151
|
|
|
162
152
|
|
|
163
|
-
@dataclass(frozen=True, slots=True)
|
|
164
|
-
class UnlockCalled(Event):
|
|
165
|
-
def __str__(self) -> str:
|
|
166
|
-
return "An `unlock` method has been called."
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"""
|
|
170
|
-
Broker
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@runtime_checkable
|
|
175
|
-
class Broker(Protocol):
|
|
176
|
-
__slots__ = ()
|
|
177
|
-
|
|
178
|
-
@abstractmethod
|
|
179
|
-
def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
|
|
180
|
-
raise NotImplementedError
|
|
181
|
-
|
|
182
|
-
@abstractmethod
|
|
183
|
-
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
184
|
-
raise NotImplementedError
|
|
185
|
-
|
|
186
|
-
@property
|
|
187
|
-
@abstractmethod
|
|
188
|
-
def is_locked(self) -> bool:
|
|
189
|
-
raise NotImplementedError
|
|
190
|
-
|
|
191
|
-
@abstractmethod
|
|
192
|
-
def unsafe_unlocking(self) -> None:
|
|
193
|
-
raise NotImplementedError
|
|
194
|
-
|
|
195
|
-
@abstractmethod
|
|
196
|
-
async def all_ready(self) -> None:
|
|
197
|
-
raise NotImplementedError
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"""
|
|
201
|
-
Locator
|
|
202
|
-
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
class Mode(StrEnum):
|
|
206
|
-
FALLBACK = "fallback"
|
|
207
|
-
NORMAL = "normal"
|
|
208
|
-
OVERRIDE = "override"
|
|
209
|
-
|
|
210
|
-
@property
|
|
211
|
-
def rank(self) -> int:
|
|
212
|
-
return tuple(type(self)).index(self)
|
|
213
|
-
|
|
214
|
-
@classmethod
|
|
215
|
-
def get_default(cls) -> Mode:
|
|
216
|
-
return cls.NORMAL
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
type ModeStr = Literal["fallback", "normal", "override"]
|
|
220
|
-
|
|
221
|
-
type InjectableFactory[T] = Callable[[Caller[..., T]], Injectable[T]]
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class Record[T](NamedTuple):
|
|
225
|
-
injectable: Injectable[T]
|
|
226
|
-
mode: Mode
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
@dataclass(repr=False, eq=False, frozen=True, kw_only=True, slots=True)
|
|
230
|
-
class Updater[T]:
|
|
231
|
-
classes: Iterable[InputType[T]]
|
|
232
|
-
injectable: Injectable[T]
|
|
233
|
-
mode: Mode
|
|
234
|
-
|
|
235
|
-
def make_record(self) -> Record[T]:
|
|
236
|
-
return Record(self.injectable, self.mode)
|
|
237
|
-
|
|
238
|
-
@classmethod
|
|
239
|
-
def with_basics(
|
|
240
|
-
cls,
|
|
241
|
-
on: TypeInfo[T],
|
|
242
|
-
/,
|
|
243
|
-
injectable: Injectable[T],
|
|
244
|
-
mode: Mode | ModeStr,
|
|
245
|
-
) -> Self:
|
|
246
|
-
return cls(
|
|
247
|
-
classes=get_return_types(on),
|
|
248
|
-
injectable=injectable,
|
|
249
|
-
mode=Mode(mode),
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
254
|
-
class Locator(Broker):
|
|
255
|
-
__records: dict[InputType[Any], Record[Any]] = field(
|
|
256
|
-
default_factory=dict,
|
|
257
|
-
init=False,
|
|
258
|
-
)
|
|
259
|
-
__channel: EventChannel = field(
|
|
260
|
-
default_factory=EventChannel,
|
|
261
|
-
init=False,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
|
|
265
|
-
for key_type in self.__iter_key_types((cls,)):
|
|
266
|
-
try:
|
|
267
|
-
record = self.__records[key_type]
|
|
268
|
-
except KeyError:
|
|
269
|
-
continue
|
|
270
|
-
|
|
271
|
-
return record.injectable
|
|
272
|
-
|
|
273
|
-
raise NoInjectable(cls)
|
|
274
|
-
|
|
275
|
-
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
276
|
-
return any(
|
|
277
|
-
key_type in self.__records for key_type in self.__iter_key_types((cls,))
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
@property
|
|
281
|
-
def is_locked(self) -> bool:
|
|
282
|
-
return any(injectable.is_locked for injectable in self.__injectables)
|
|
283
|
-
|
|
284
|
-
@property
|
|
285
|
-
def __injectables(self) -> frozenset[Injectable[Any]]:
|
|
286
|
-
return frozenset(record.injectable for record in self.__records.values())
|
|
287
|
-
|
|
288
|
-
def update[T](self, updater: Updater[T]) -> Self:
|
|
289
|
-
record = updater.make_record()
|
|
290
|
-
key_types = self.__build_key_types(updater.classes)
|
|
291
|
-
records = dict(self.__prepare_for_updating(key_types, record))
|
|
292
|
-
|
|
293
|
-
if records:
|
|
294
|
-
event = LocatorDependenciesUpdated(self, records.keys(), record.mode)
|
|
295
|
-
|
|
296
|
-
with self.dispatch(event):
|
|
297
|
-
self.__records.update(records)
|
|
298
|
-
|
|
299
|
-
return self
|
|
300
|
-
|
|
301
|
-
def unsafe_unlocking(self) -> None:
|
|
302
|
-
for injectable in self.__injectables:
|
|
303
|
-
injectable.unlock()
|
|
304
|
-
|
|
305
|
-
async def all_ready(self) -> None:
|
|
306
|
-
for injectable in self.__injectables:
|
|
307
|
-
if injectable.is_locked:
|
|
308
|
-
continue
|
|
309
|
-
|
|
310
|
-
with suppress(SkipInjectable):
|
|
311
|
-
await injectable.aget_instance()
|
|
312
|
-
|
|
313
|
-
def add_listener(self, listener: EventListener) -> Self:
|
|
314
|
-
self.__channel.add_listener(listener)
|
|
315
|
-
return self
|
|
316
|
-
|
|
317
|
-
def dispatch(self, event: Event) -> ContextManager[None]:
|
|
318
|
-
return self.__channel.dispatch(event)
|
|
319
|
-
|
|
320
|
-
def __prepare_for_updating[T](
|
|
321
|
-
self,
|
|
322
|
-
classes: Iterable[InputType[T]],
|
|
323
|
-
record: Record[T],
|
|
324
|
-
) -> Iterator[tuple[InputType[T], Record[T]]]:
|
|
325
|
-
for cls in classes:
|
|
326
|
-
try:
|
|
327
|
-
existing = self.__records[cls]
|
|
328
|
-
except KeyError:
|
|
329
|
-
...
|
|
330
|
-
else:
|
|
331
|
-
if not self.__keep_new_record(record, existing, cls):
|
|
332
|
-
continue
|
|
333
|
-
|
|
334
|
-
yield cls, record
|
|
335
|
-
|
|
336
|
-
@staticmethod
|
|
337
|
-
def __build_key_types[T](classes: Iterable[InputType[T]]) -> frozenset[TypeDef[T]]:
|
|
338
|
-
config = MatchingTypesConfig(ignore_none=True)
|
|
339
|
-
return frozenset(
|
|
340
|
-
itertools.chain.from_iterable(
|
|
341
|
-
iter_matching_types(cls, config) for cls in classes
|
|
342
|
-
)
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
@staticmethod
|
|
346
|
-
def __iter_key_types[T](classes: Iterable[InputType[T]]) -> Iterator[InputType[T]]:
|
|
347
|
-
config = MatchingTypesConfig(with_origin=True, with_type_alias_value=True)
|
|
348
|
-
for cls in classes:
|
|
349
|
-
yield from iter_matching_types(cls, config)
|
|
350
|
-
|
|
351
|
-
@staticmethod
|
|
352
|
-
def __keep_new_record[T](
|
|
353
|
-
new: Record[T],
|
|
354
|
-
existing: Record[T],
|
|
355
|
-
cls: InputType[T],
|
|
356
|
-
) -> bool:
|
|
357
|
-
new_mode, existing_mode = new.mode, existing.mode
|
|
358
|
-
|
|
359
|
-
if new_mode == Mode.OVERRIDE:
|
|
360
|
-
return True
|
|
361
|
-
|
|
362
|
-
elif new_mode == existing_mode:
|
|
363
|
-
raise RuntimeError(f"An injectable already exists for the class `{cls}`.")
|
|
364
|
-
|
|
365
|
-
return new_mode.rank > existing_mode.rank
|
|
366
|
-
|
|
367
|
-
|
|
368
153
|
"""
|
|
369
154
|
Module
|
|
370
155
|
"""
|
|
@@ -387,11 +172,10 @@ type ContextManagerLikeRecipe[**P, T] = (
|
|
|
387
172
|
type GeneratorRecipe[**P, T] = (
|
|
388
173
|
Callable[P, Generator[T, Any, Any]] | Callable[P, AsyncGenerator[T, Any]]
|
|
389
174
|
)
|
|
390
|
-
type Recipe[**P, T] = Callable[P, T] | Callable[P, Awaitable[T]]
|
|
391
175
|
|
|
392
176
|
|
|
393
177
|
@dataclass(eq=False, frozen=True, slots=True)
|
|
394
|
-
class Module(
|
|
178
|
+
class Module(EventListener, InjectionProvider): # type: ignore[misc]
|
|
395
179
|
name: str = field(default_factory=lambda: f"anonymous@{new_short_key()}")
|
|
396
180
|
__channel: EventChannel = field(
|
|
397
181
|
default_factory=EventChannel,
|
|
@@ -420,23 +204,26 @@ class Module(Broker, EventListener):
|
|
|
420
204
|
self.__locator.add_listener(self)
|
|
421
205
|
|
|
422
206
|
def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
207
|
+
key_types = self.__matching_key_types(cls)
|
|
208
|
+
|
|
209
|
+
for locator in self._iter_locators():
|
|
210
|
+
for key_type in key_types:
|
|
211
|
+
with suppress(KeyError):
|
|
212
|
+
return locator.request(key_type, self)
|
|
426
213
|
|
|
427
214
|
raise NoInjectable(cls)
|
|
428
215
|
|
|
429
216
|
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
430
|
-
|
|
217
|
+
key_types = self.__matching_key_types(cls)
|
|
218
|
+
return any(
|
|
219
|
+
key_type in locator
|
|
220
|
+
for locator in self._iter_locators()
|
|
221
|
+
for key_type in key_types
|
|
222
|
+
)
|
|
431
223
|
|
|
432
224
|
@property
|
|
433
225
|
def is_locked(self) -> bool:
|
|
434
|
-
return any(
|
|
435
|
-
|
|
436
|
-
@property
|
|
437
|
-
def __brokers(self) -> Iterator[Broker]:
|
|
438
|
-
yield from self.__modules
|
|
439
|
-
yield self.__locator
|
|
226
|
+
return any(locator.is_locked(self) for locator in self._iter_locators())
|
|
440
227
|
|
|
441
228
|
def injectable[**P, T](
|
|
442
229
|
self,
|
|
@@ -450,11 +237,13 @@ class Module(Broker, EventListener):
|
|
|
450
237
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
451
238
|
) -> Any:
|
|
452
239
|
def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
|
|
453
|
-
factory = extract_caller(self.make_injected_function(wp) if inject else wp)
|
|
454
|
-
injectable = cls(factory) # type: ignore[arg-type]
|
|
455
240
|
hints = on if ignore_type_hint else (wp, on)
|
|
456
|
-
|
|
457
|
-
|
|
241
|
+
broker = (
|
|
242
|
+
DynamicInjectableBroker(cls, wp)
|
|
243
|
+
if inject
|
|
244
|
+
else StaticInjectableBroker.from_factory(cls, wp)
|
|
245
|
+
)
|
|
246
|
+
self.update_from(hints, broker, mode)
|
|
458
247
|
return wp
|
|
459
248
|
|
|
460
249
|
return decorator(wrapped) if wrapped else decorator
|
|
@@ -505,8 +294,8 @@ class Module(Broker, EventListener):
|
|
|
505
294
|
def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
|
|
506
295
|
def decorator(wp: type[T]) -> type[T]:
|
|
507
296
|
injectable = ShouldBeInjectable(wp)
|
|
508
|
-
|
|
509
|
-
self.
|
|
297
|
+
broker = StaticInjectableBroker(injectable)
|
|
298
|
+
self.update_from(wp, broker, Mode.FALLBACK)
|
|
510
299
|
return wp
|
|
511
300
|
|
|
512
301
|
return decorator(wrapped) if wrapped else decorator
|
|
@@ -564,8 +353,8 @@ class Module(Broker, EventListener):
|
|
|
564
353
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
565
354
|
) -> SlotKey[T]:
|
|
566
355
|
injectable = ScopedSlotInjectable(cls, scope_name)
|
|
567
|
-
|
|
568
|
-
self.
|
|
356
|
+
broker = StaticInjectableBroker(injectable)
|
|
357
|
+
self.update_from(cls, broker, mode)
|
|
569
358
|
return injectable.key
|
|
570
359
|
|
|
571
360
|
def inject[**P, T](
|
|
@@ -630,7 +419,7 @@ class Module(Broker, EventListener):
|
|
|
630
419
|
wrapped,
|
|
631
420
|
threadsafe,
|
|
632
421
|
)
|
|
633
|
-
return factory.
|
|
422
|
+
return factory.__injection_metadata__.acall
|
|
634
423
|
|
|
635
424
|
async def afind_instance[T](
|
|
636
425
|
self,
|
|
@@ -747,7 +536,7 @@ class Module(Broker, EventListener):
|
|
|
747
536
|
lambda instance=default: instance,
|
|
748
537
|
threadsafe=threadsafe,
|
|
749
538
|
)
|
|
750
|
-
metadata = function.
|
|
539
|
+
metadata = function.__injection_metadata__.set_owner(cls)
|
|
751
540
|
return SimpleAwaitable(metadata.acall)
|
|
752
541
|
|
|
753
542
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -781,13 +570,28 @@ class Module(Broker, EventListener):
|
|
|
781
570
|
lambda instance=default: instance,
|
|
782
571
|
threadsafe=threadsafe,
|
|
783
572
|
)
|
|
784
|
-
metadata = function.
|
|
573
|
+
metadata = function.__injection_metadata__.set_owner(cls)
|
|
785
574
|
return SimpleInvertible(metadata.call)
|
|
786
575
|
|
|
787
576
|
def update[T](self, updater: Updater[T]) -> Self:
|
|
788
577
|
self.__locator.update(updater)
|
|
789
578
|
return self
|
|
790
579
|
|
|
580
|
+
def update_from[T](
|
|
581
|
+
self,
|
|
582
|
+
on: TypeInfo[T],
|
|
583
|
+
/,
|
|
584
|
+
broker: InjectableBroker[T],
|
|
585
|
+
mode: Mode | ModeStr,
|
|
586
|
+
) -> Self:
|
|
587
|
+
updater = Updater(
|
|
588
|
+
classes=self.__build_key_types(on),
|
|
589
|
+
broker=broker,
|
|
590
|
+
mode=Mode(mode),
|
|
591
|
+
)
|
|
592
|
+
self.update(updater)
|
|
593
|
+
return self
|
|
594
|
+
|
|
791
595
|
def init_modules(self, *modules: Module) -> Self:
|
|
792
596
|
for module in tuple(self.__modules):
|
|
793
597
|
self.stop_using(module)
|
|
@@ -857,20 +661,14 @@ class Module(Broker, EventListener):
|
|
|
857
661
|
return self
|
|
858
662
|
|
|
859
663
|
def unlock(self) -> Self:
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
with self.dispatch(event, lock_bypass=True):
|
|
863
|
-
self.unsafe_unlocking()
|
|
664
|
+
for locator in self._iter_locators():
|
|
665
|
+
locator.unlock(self)
|
|
864
666
|
|
|
865
667
|
return self
|
|
866
668
|
|
|
867
|
-
def unsafe_unlocking(self) -> None:
|
|
868
|
-
for broker in self.__brokers:
|
|
869
|
-
broker.unsafe_unlocking()
|
|
870
|
-
|
|
871
669
|
async def all_ready(self) -> None:
|
|
872
|
-
for
|
|
873
|
-
await
|
|
670
|
+
for locator in self._iter_locators():
|
|
671
|
+
await locator.all_ready(self)
|
|
874
672
|
|
|
875
673
|
def add_logger(self, logger: Logger) -> Self:
|
|
876
674
|
self.__loggers.append(logger)
|
|
@@ -889,9 +687,8 @@ class Module(Broker, EventListener):
|
|
|
889
687
|
return self.dispatch(self_event)
|
|
890
688
|
|
|
891
689
|
@contextmanager
|
|
892
|
-
def dispatch(self, event: Event
|
|
893
|
-
|
|
894
|
-
self.__check_locking()
|
|
690
|
+
def dispatch(self, event: Event) -> Iterator[None]:
|
|
691
|
+
self.__check_locking()
|
|
895
692
|
|
|
896
693
|
with self.__channel.dispatch(event):
|
|
897
694
|
try:
|
|
@@ -899,6 +696,12 @@ class Module(Broker, EventListener):
|
|
|
899
696
|
finally:
|
|
900
697
|
self.__debug(event)
|
|
901
698
|
|
|
699
|
+
def _iter_locators(self) -> Iterator[Locator]:
|
|
700
|
+
for module in self.__modules:
|
|
701
|
+
yield from module._iter_locators()
|
|
702
|
+
|
|
703
|
+
yield self.__locator
|
|
704
|
+
|
|
902
705
|
def __debug(self, message: object) -> None:
|
|
903
706
|
for logger in self.__loggers:
|
|
904
707
|
logger.debug(message)
|
|
@@ -930,6 +733,20 @@ class Module(Broker, EventListener):
|
|
|
930
733
|
def default(cls) -> Module:
|
|
931
734
|
return cls.from_name("__default__")
|
|
932
735
|
|
|
736
|
+
@staticmethod
|
|
737
|
+
def __build_key_types(input_cls: Any) -> frozenset[Any]:
|
|
738
|
+
config = MatchingTypesConfig(ignore_none=True)
|
|
739
|
+
return frozenset(
|
|
740
|
+
itertools.chain.from_iterable(
|
|
741
|
+
iter_matching_types(cls, config) for cls in get_return_types(input_cls)
|
|
742
|
+
)
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
@staticmethod
|
|
746
|
+
def __matching_key_types(input_cls: Any) -> tuple[Any, ...]:
|
|
747
|
+
config = MatchingTypesConfig(with_origin=True, with_type_alias_value=True)
|
|
748
|
+
return matching_types(input_cls, config)
|
|
749
|
+
|
|
933
750
|
|
|
934
751
|
def mod(name: str | None = None, /) -> Module:
|
|
935
752
|
if name is None:
|
|
@@ -1151,24 +968,24 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
|
1151
968
|
task()
|
|
1152
969
|
|
|
1153
970
|
|
|
1154
|
-
class InjectedFunction[**P, T](ABC):
|
|
1155
|
-
__slots__ = ("__dict__", "
|
|
971
|
+
class InjectedFunction[**P, T](HiddenCaller[P, T], ABC):
|
|
972
|
+
__slots__ = ("__dict__", "__injection_metadata__")
|
|
1156
973
|
|
|
1157
|
-
|
|
974
|
+
__injection_metadata__: InjectMetadata[P, T]
|
|
1158
975
|
|
|
1159
976
|
def __init__(self, metadata: InjectMetadata[P, T]) -> None:
|
|
1160
977
|
update_wrapper(self, metadata.wrapped)
|
|
1161
|
-
self.
|
|
978
|
+
self.__injection_metadata__ = metadata
|
|
1162
979
|
|
|
1163
980
|
def __repr__(self) -> str: # pragma: no cover
|
|
1164
|
-
return repr(self.
|
|
981
|
+
return repr(self.__injection_metadata__.wrapped)
|
|
1165
982
|
|
|
1166
983
|
def __str__(self) -> str: # pragma: no cover
|
|
1167
|
-
return str(self.
|
|
984
|
+
return str(self.__injection_metadata__.wrapped)
|
|
1168
985
|
|
|
1169
|
-
@
|
|
1170
|
-
def
|
|
1171
|
-
|
|
986
|
+
@property
|
|
987
|
+
def __injection_hidden_caller__(self) -> Caller[P, T]:
|
|
988
|
+
return self.__injection_metadata__
|
|
1172
989
|
|
|
1173
990
|
def __get__(
|
|
1174
991
|
self,
|
|
@@ -1181,7 +998,7 @@ class InjectedFunction[**P, T](ABC):
|
|
|
1181
998
|
return MethodType(self, instance)
|
|
1182
999
|
|
|
1183
1000
|
def __set_name__(self, owner: type, name: str) -> None:
|
|
1184
|
-
self.
|
|
1001
|
+
self.__injection_metadata__.set_owner(owner)
|
|
1185
1002
|
|
|
1186
1003
|
|
|
1187
1004
|
class AsyncInjectedFunction[**P, T](InjectedFunction[P, Awaitable[T]]):
|
|
@@ -1192,23 +1009,11 @@ class AsyncInjectedFunction[**P, T](InjectedFunction[P, Awaitable[T]]):
|
|
|
1192
1009
|
markcoroutinefunction(self)
|
|
1193
1010
|
|
|
1194
1011
|
async def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
1195
|
-
return await (await self.
|
|
1012
|
+
return await (await self.__injection_metadata__.acall(*args, **kwargs))
|
|
1196
1013
|
|
|
1197
1014
|
|
|
1198
1015
|
class SyncInjectedFunction[**P, T](InjectedFunction[P, T]):
|
|
1199
1016
|
__slots__ = ()
|
|
1200
1017
|
|
|
1201
1018
|
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
1202
|
-
return self.
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
def extract_caller[**P, T](
|
|
1206
|
-
function: Callable[P, T] | Callable[P, Awaitable[T]],
|
|
1207
|
-
) -> Caller[P, T]:
|
|
1208
|
-
if iscoroutinefunction(function):
|
|
1209
|
-
return AsyncCaller(function)
|
|
1210
|
-
|
|
1211
|
-
elif isinstance(function, InjectedFunction):
|
|
1212
|
-
return function.__inject_metadata__
|
|
1213
|
-
|
|
1214
|
-
return SyncCaller(function) # type: ignore[arg-type]
|
|
1019
|
+
return self.__injection_metadata__.call(*args, **kwargs)
|
|
@@ -182,11 +182,11 @@ def get_scope[T](name: str, default: T | EllipsisType = ...) -> Scope | T:
|
|
|
182
182
|
return default
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
def in_scope_cache(key: Any, scope_name: str) -> bool:
|
|
185
|
+
def in_scope_cache(key: SlotKey[Any], scope_name: str) -> bool:
|
|
186
186
|
return any(key in scope.cache for scope in get_active_scopes(scope_name))
|
|
187
187
|
|
|
188
188
|
|
|
189
|
-
def remove_scoped_values(key: Any, scope_name: str) -> None:
|
|
189
|
+
def remove_scoped_values(key: SlotKey[Any], scope_name: str) -> None:
|
|
190
190
|
for scope in get_active_scopes(scope_name):
|
|
191
191
|
scope.cache.pop(key, None)
|
|
192
192
|
|
|
@@ -233,7 +233,7 @@ def _bind_scope(
|
|
|
233
233
|
class Scope(Protocol):
|
|
234
234
|
__slots__ = ()
|
|
235
235
|
|
|
236
|
-
cache: MutableMapping[Any, Any]
|
|
236
|
+
cache: MutableMapping[SlotKey[Any], Any]
|
|
237
237
|
|
|
238
238
|
@abstractmethod
|
|
239
239
|
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
|
|
@@ -247,7 +247,7 @@ class Scope(Protocol):
|
|
|
247
247
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
248
248
|
class BaseScope[T](Scope, ABC):
|
|
249
249
|
delegate: T
|
|
250
|
-
cache: MutableMapping[Any, Any] = field(
|
|
250
|
+
cache: MutableMapping[SlotKey[Any], Any] = field(
|
|
251
251
|
default_factory=dict,
|
|
252
252
|
init=False,
|
|
253
253
|
hash=False,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from injection import mod
|
|
4
|
+
from injection.loaders import LoadedProfile, ProfileLoader, load_profile
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"TEST_PROFILE_NAME",
|
|
8
|
+
"load_test_profile",
|
|
9
|
+
"reserve_scoped_test_slot",
|
|
10
|
+
"set_test_constant",
|
|
11
|
+
"should_be_test_injectable",
|
|
12
|
+
"test_constant",
|
|
13
|
+
"test_injectable",
|
|
14
|
+
"test_scoped",
|
|
15
|
+
"test_singleton",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TEST_PROFILE_NAME: Final[str] = "__testing__"
|
|
19
|
+
|
|
20
|
+
reserve_scoped_test_slot = mod(TEST_PROFILE_NAME).reserve_scoped_slot
|
|
21
|
+
set_test_constant = mod(TEST_PROFILE_NAME).set_constant
|
|
22
|
+
should_be_test_injectable = mod(TEST_PROFILE_NAME).should_be_injectable
|
|
23
|
+
test_constant = mod(TEST_PROFILE_NAME).constant
|
|
24
|
+
test_injectable = mod(TEST_PROFILE_NAME).injectable
|
|
25
|
+
test_scoped = mod(TEST_PROFILE_NAME).scoped
|
|
26
|
+
test_singleton = mod(TEST_PROFILE_NAME).singleton
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_test_profile(loader: ProfileLoader | None = None) -> LoadedProfile:
|
|
30
|
+
return load_profile(TEST_PROFILE_NAME, loader)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from typing import Final
|
|
2
|
-
|
|
3
|
-
from injection import mod
|
|
4
|
-
from injection.loaders import LoadedProfile, ProfileLoader, load_profile
|
|
5
|
-
|
|
6
|
-
__all__ = (
|
|
7
|
-
"load_test_profile",
|
|
8
|
-
"reserve_scoped_test_slot",
|
|
9
|
-
"set_test_constant",
|
|
10
|
-
"should_be_test_injectable",
|
|
11
|
-
"test_constant",
|
|
12
|
-
"test_injectable",
|
|
13
|
-
"test_scoped",
|
|
14
|
-
"test_singleton",
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
_TEST_PROFILE_NAME: Final[str] = "__testing__"
|
|
18
|
-
|
|
19
|
-
reserve_scoped_test_slot = mod(_TEST_PROFILE_NAME).reserve_scoped_slot
|
|
20
|
-
set_test_constant = mod(_TEST_PROFILE_NAME).set_constant
|
|
21
|
-
should_be_test_injectable = mod(_TEST_PROFILE_NAME).should_be_injectable
|
|
22
|
-
test_constant = mod(_TEST_PROFILE_NAME).constant
|
|
23
|
-
test_injectable = mod(_TEST_PROFILE_NAME).injectable
|
|
24
|
-
test_scoped = mod(_TEST_PROFILE_NAME).scoped
|
|
25
|
-
test_singleton = mod(_TEST_PROFILE_NAME).singleton
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def load_test_profile(loader: ProfileLoader | None = None) -> LoadedProfile:
|
|
29
|
-
return load_profile(_TEST_PROFILE_NAME, loader)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|