python-injection 0.4.2__tar.gz → 0.5.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.
Potentially problematic release.
This version of python-injection might be problematic. Click here for more details.
- {python_injection-0.4.2 → python_injection-0.5.1}/PKG-INFO +6 -6
- python_injection-0.4.2/documentation/how-to-use.md → python_injection-0.5.1/documentation/basic-usage.md +3 -3
- python_injection-0.5.1/injection/__init__.py +19 -0
- {python_injection-0.4.2 → python_injection-0.5.1}/injection/common/event.py +8 -1
- {python_injection-0.4.2 → python_injection-0.5.1}/injection/common/lazy.py +1 -1
- python_injection-0.5.1/injection/core/module.py +573 -0
- python_injection-0.5.1/injection/exceptions.py +13 -0
- {python_injection-0.4.2 → python_injection-0.5.1}/injection/utils.py +4 -0
- {python_injection-0.4.2 → python_injection-0.5.1}/pyproject.toml +3 -3
- python_injection-0.4.2/injection/__init__.py +0 -17
- python_injection-0.4.2/injection/core/module.py +0 -335
- python_injection-0.4.2/injection/core/module.pyi +0 -44
- python_injection-0.4.2/injection/exceptions.py +0 -9
- python_injection-0.4.2/injection/utils.pyi +0 -6
- {python_injection-0.4.2 → python_injection-0.5.1}/injection/common/__init__.py +0 -0
- {python_injection-0.4.2 → python_injection-0.5.1}/injection/core/__init__.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-injection
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Fast and easy dependency injection framework.
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/simplysquare/python-injection
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: dependencies,inject,injection
|
|
8
8
|
Author: remimd
|
|
@@ -13,10 +13,10 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Requires-Dist: frozendict
|
|
16
|
-
Project-URL: Repository, https://github.com/
|
|
16
|
+
Project-URL: Repository, https://github.com/simplysquare/python-injection
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Basic usage
|
|
20
20
|
|
|
21
21
|
## Create an injectable
|
|
22
22
|
|
|
@@ -108,12 +108,12 @@ class C(B):
|
|
|
108
108
|
## Recipes
|
|
109
109
|
|
|
110
110
|
A recipe is a function that tells the injector how to construct the instance to be injected. It is important to specify
|
|
111
|
-
the
|
|
111
|
+
the return type annotation when defining the recipe.
|
|
112
112
|
|
|
113
113
|
```python
|
|
114
114
|
from injection import singleton
|
|
115
115
|
|
|
116
|
-
@singleton
|
|
116
|
+
@singleton
|
|
117
117
|
def my_recipe() -> Singleton:
|
|
118
118
|
""" recipe implementation """
|
|
119
119
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Basic usage
|
|
2
2
|
|
|
3
3
|
## Create an injectable
|
|
4
4
|
|
|
@@ -90,12 +90,12 @@ class C(B):
|
|
|
90
90
|
## Recipes
|
|
91
91
|
|
|
92
92
|
A recipe is a function that tells the injector how to construct the instance to be injected. It is important to specify
|
|
93
|
-
the
|
|
93
|
+
the return type annotation when defining the recipe.
|
|
94
94
|
|
|
95
95
|
```python
|
|
96
96
|
from injection import singleton
|
|
97
97
|
|
|
98
|
-
@singleton
|
|
98
|
+
@singleton
|
|
99
99
|
def my_recipe() -> Singleton:
|
|
100
100
|
""" recipe implementation """
|
|
101
101
|
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .core import Injectable, Module, ModulePriorities
|
|
2
|
+
|
|
3
|
+
__all__ = (
|
|
4
|
+
"Injectable",
|
|
5
|
+
"Module",
|
|
6
|
+
"ModulePriorities",
|
|
7
|
+
"default_module",
|
|
8
|
+
"get_instance",
|
|
9
|
+
"inject",
|
|
10
|
+
"injectable",
|
|
11
|
+
"singleton",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
default_module = Module(f"{__name__}:default_module")
|
|
15
|
+
|
|
16
|
+
get_instance = default_module.get_instance
|
|
17
|
+
inject = default_module.inject
|
|
18
|
+
injectable = default_module.injectable
|
|
19
|
+
singleton = default_module.singleton
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from contextlib import suppress
|
|
2
3
|
from dataclasses import dataclass, field
|
|
3
4
|
from weakref import WeakSet
|
|
4
5
|
|
|
@@ -17,7 +18,7 @@ class EventListener(ABC):
|
|
|
17
18
|
raise NotImplementedError
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
21
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
21
22
|
class EventChannel:
|
|
22
23
|
__listeners: WeakSet[EventListener] = field(default_factory=WeakSet, init=False)
|
|
23
24
|
|
|
@@ -30,3 +31,9 @@ class EventChannel:
|
|
|
30
31
|
def add_listener(self, listener: EventListener):
|
|
31
32
|
self.__listeners.add(listener)
|
|
32
33
|
return self
|
|
34
|
+
|
|
35
|
+
def remove_listener(self, listener: EventListener):
|
|
36
|
+
with suppress(KeyError):
|
|
37
|
+
self.__listeners.remove(listener)
|
|
38
|
+
|
|
39
|
+
return self
|
|
@@ -22,7 +22,7 @@ class Lazy(Generic[T]):
|
|
|
22
22
|
|
|
23
23
|
def __setattr__(self, name: str, value: Any):
|
|
24
24
|
if self.is_set:
|
|
25
|
-
raise TypeError(f"`{repr(self)}` is frozen.")
|
|
25
|
+
raise TypeError(f"`{repr(self)}` is frozen.")
|
|
26
26
|
|
|
27
27
|
return super().__setattr__(name, value)
|
|
28
28
|
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from collections import ChainMap, OrderedDict
|
|
6
|
+
from contextlib import ContextDecorator, contextmanager
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum, auto
|
|
9
|
+
from functools import cached_property, singledispatchmethod, wraps
|
|
10
|
+
from inspect import Signature, get_annotations
|
|
11
|
+
from logging import getLogger
|
|
12
|
+
from types import MappingProxyType
|
|
13
|
+
from typing import (
|
|
14
|
+
Any,
|
|
15
|
+
Callable,
|
|
16
|
+
Iterable,
|
|
17
|
+
Iterator,
|
|
18
|
+
Mapping,
|
|
19
|
+
NamedTuple,
|
|
20
|
+
Protocol,
|
|
21
|
+
TypeVar,
|
|
22
|
+
cast,
|
|
23
|
+
final,
|
|
24
|
+
get_origin,
|
|
25
|
+
runtime_checkable,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from injection.common.event import Event, EventChannel, EventListener
|
|
29
|
+
from injection.common.lazy import LazyMapping
|
|
30
|
+
from injection.exceptions import ModuleError, NoInjectable
|
|
31
|
+
|
|
32
|
+
__all__ = ("Injectable", "Module", "ModulePriorities")
|
|
33
|
+
|
|
34
|
+
_logger = getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
T = TypeVar("T")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _format_type(cls: type) -> str:
|
|
40
|
+
try:
|
|
41
|
+
return f"{cls.__module__}.{cls.__qualname__}"
|
|
42
|
+
except AttributeError:
|
|
43
|
+
return str(cls)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
Events
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class ContainerEvent(Event, ABC):
|
|
53
|
+
on_container: Container
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class ContainerDependenciesUpdated(ContainerEvent):
|
|
58
|
+
references: set[type]
|
|
59
|
+
|
|
60
|
+
def __str__(self) -> str:
|
|
61
|
+
length = len(self.references)
|
|
62
|
+
formatted_references = ", ".join(
|
|
63
|
+
f"`{_format_type(reference)}`" for reference in self.references
|
|
64
|
+
)
|
|
65
|
+
return (
|
|
66
|
+
f"{length} container dependenc{'ies' if length > 1 else 'y'} have been "
|
|
67
|
+
f"updated{f': {formatted_references}' if formatted_references else ''}."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class ModuleEvent(Event, ABC):
|
|
73
|
+
on_module: Module
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class ModuleEventProxy(ModuleEvent):
|
|
78
|
+
event: Event
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
return f"`{self.on_module}` has propagated an event: {self.origin}"
|
|
82
|
+
|
|
83
|
+
@cached_property
|
|
84
|
+
def origin(self) -> Event:
|
|
85
|
+
if isinstance(self.event, ModuleEventProxy):
|
|
86
|
+
return self.event.origin
|
|
87
|
+
|
|
88
|
+
return self.event
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def is_circular(self) -> bool:
|
|
92
|
+
origin = self.origin
|
|
93
|
+
return isinstance(origin, ModuleEvent) and origin.on_module is self.on_module
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def previous_module(self) -> Module | None:
|
|
97
|
+
if isinstance(self.event, ModuleEvent):
|
|
98
|
+
return self.event.on_module
|
|
99
|
+
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True, slots=True)
|
|
104
|
+
class ModuleAdded(ModuleEvent):
|
|
105
|
+
module_added: Module
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return f"`{self.on_module}` now uses `{self.module_added}`."
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True, slots=True)
|
|
112
|
+
class ModuleRemoved(ModuleEvent):
|
|
113
|
+
module_removed: Module
|
|
114
|
+
|
|
115
|
+
def __str__(self) -> str:
|
|
116
|
+
return f"`{self.on_module}` no longer uses `{self.module_removed}`."
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True, slots=True)
|
|
120
|
+
class ModulePriorityUpdated(ModuleEvent):
|
|
121
|
+
module_updated: Module
|
|
122
|
+
priority: ModulePriorities
|
|
123
|
+
|
|
124
|
+
def __str__(self) -> str:
|
|
125
|
+
return (
|
|
126
|
+
f"In `{self.on_module}`, the priority `{self.priority.name}` "
|
|
127
|
+
f"has been applied to `{self.module_updated}`."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
Injectables
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@runtime_checkable
|
|
137
|
+
class Injectable(Protocol[T]):
|
|
138
|
+
__slots__ = ()
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def get_instance(self) -> T:
|
|
142
|
+
raise NotImplementedError
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
146
|
+
class _BaseInjectable(Injectable[T], ABC):
|
|
147
|
+
factory: Callable[[], T]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class NewInjectable(_BaseInjectable[T]):
|
|
151
|
+
__slots__ = ()
|
|
152
|
+
|
|
153
|
+
def get_instance(self) -> T:
|
|
154
|
+
return self.factory()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class SingletonInjectable(_BaseInjectable[T]):
|
|
158
|
+
@cached_property
|
|
159
|
+
def __instance(self) -> T:
|
|
160
|
+
return self.factory()
|
|
161
|
+
|
|
162
|
+
def get_instance(self) -> T:
|
|
163
|
+
return self.__instance
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
Container
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
172
|
+
class Container:
|
|
173
|
+
__data: dict[type, Injectable] = field(default_factory=dict, init=False)
|
|
174
|
+
__channel: EventChannel = field(default_factory=EventChannel, init=False)
|
|
175
|
+
|
|
176
|
+
def __getitem__(self, reference: type[T]) -> Injectable[T]:
|
|
177
|
+
cls = origin if (origin := get_origin(reference)) else reference
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
return self.__data[cls]
|
|
181
|
+
except KeyError as exc:
|
|
182
|
+
raise NoInjectable(f"No injectable for `{_format_type(cls)}`.") from exc
|
|
183
|
+
|
|
184
|
+
def set_multiple(self, references: Iterable[type], injectable: Injectable):
|
|
185
|
+
if not isinstance(references, set):
|
|
186
|
+
references = set(references)
|
|
187
|
+
|
|
188
|
+
if references:
|
|
189
|
+
new_values = (
|
|
190
|
+
(self.check_if_exists(reference), injectable)
|
|
191
|
+
for reference in references
|
|
192
|
+
)
|
|
193
|
+
self.__data.update(new_values)
|
|
194
|
+
event = ContainerDependenciesUpdated(self, references)
|
|
195
|
+
self.notify(event)
|
|
196
|
+
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
def check_if_exists(self, reference: type) -> type:
|
|
200
|
+
if reference in self.__data:
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
"An injectable already exists for the reference "
|
|
203
|
+
f"class `{_format_type(reference)}`."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return reference
|
|
207
|
+
|
|
208
|
+
def add_listener(self, listener: EventListener):
|
|
209
|
+
self.__channel.add_listener(listener)
|
|
210
|
+
return self
|
|
211
|
+
|
|
212
|
+
def notify(self, event: Event):
|
|
213
|
+
self.__channel.dispatch(event)
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
Module
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class ModulePriorities(Enum):
|
|
223
|
+
HIGH = auto()
|
|
224
|
+
LOW = auto()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
228
|
+
class Module(EventListener):
|
|
229
|
+
"""
|
|
230
|
+
Object with isolated injection environment.
|
|
231
|
+
|
|
232
|
+
Modules have been designed to simplify unit test writing. So think carefully before
|
|
233
|
+
instantiating a new one. They could increase complexity unnecessarily if used
|
|
234
|
+
extensively.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
name: str = field(default=None)
|
|
238
|
+
__container: Container = field(default_factory=Container, init=False)
|
|
239
|
+
__channel: EventChannel = field(default_factory=EventChannel, init=False)
|
|
240
|
+
__modules: OrderedDict[Module, None] = field(
|
|
241
|
+
default_factory=OrderedDict,
|
|
242
|
+
init=False,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def __post_init__(self):
|
|
246
|
+
self.__container.add_listener(self)
|
|
247
|
+
|
|
248
|
+
def __getitem__(self, reference: type[T], /) -> Injectable[T]:
|
|
249
|
+
return ChainMap(*self.__modules, self.__container)[reference]
|
|
250
|
+
|
|
251
|
+
def __setitem__(self, on: type | Iterable[type], injectable: Injectable, /):
|
|
252
|
+
references = on if isinstance(on, Iterable) else (on,)
|
|
253
|
+
self.__container.set_multiple(references, injectable)
|
|
254
|
+
|
|
255
|
+
def __str__(self) -> str:
|
|
256
|
+
return self.name or object.__str__(self)
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def inject(self) -> InjectDecorator:
|
|
260
|
+
"""
|
|
261
|
+
Decorator applicable to a class or function. Inject function dependencies using
|
|
262
|
+
parameter type annotations. If applied to a class, the dependencies resolved
|
|
263
|
+
will be those of the `__init__` method.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
return InjectDecorator(self)
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def injectable(self) -> InjectableDecorator:
|
|
270
|
+
"""
|
|
271
|
+
Decorator applicable to a class or function. It is used to indicate how the
|
|
272
|
+
injectable will be constructed. At injection time, a new instance will be
|
|
273
|
+
injected each time. Automatically injects constructor dependencies, can be
|
|
274
|
+
disabled with `auto_inject=False`.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
return InjectableDecorator(self, NewInjectable)
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def singleton(self) -> InjectableDecorator:
|
|
281
|
+
"""
|
|
282
|
+
Decorator applicable to a class or function. It is used to indicate how the
|
|
283
|
+
singleton will be constructed. At injection time, the injected instance will
|
|
284
|
+
always be the same. Automatically injects constructor dependencies, can be
|
|
285
|
+
disabled with `auto_inject=False`.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
return InjectableDecorator(self, SingletonInjectable)
|
|
289
|
+
|
|
290
|
+
def get_instance(self, reference: type[T]) -> T | None:
|
|
291
|
+
"""
|
|
292
|
+
Function used to retrieve an instance associated with the type passed in
|
|
293
|
+
parameter or return `None`.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
injectable = self[reference]
|
|
298
|
+
except KeyError:
|
|
299
|
+
return None
|
|
300
|
+
|
|
301
|
+
instance = injectable.get_instance()
|
|
302
|
+
return cast(reference, instance)
|
|
303
|
+
|
|
304
|
+
def use(
|
|
305
|
+
self,
|
|
306
|
+
module: Module,
|
|
307
|
+
priority: ModulePriorities = ModulePriorities.LOW,
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
Function for using another module. Using another module replaces the module's
|
|
311
|
+
dependencies with those of the module used. If the dependency is not found, it
|
|
312
|
+
will be searched for in the module's dependency container.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
if module is self:
|
|
316
|
+
raise ModuleError("Module can't be used by itself.")
|
|
317
|
+
|
|
318
|
+
if module in self.__modules:
|
|
319
|
+
raise ModuleError(f"`{self}` already uses `{module}`.")
|
|
320
|
+
|
|
321
|
+
self.__modules[module] = None
|
|
322
|
+
self.__move_module(module, priority)
|
|
323
|
+
module.add_listener(self)
|
|
324
|
+
event = ModuleAdded(self, module)
|
|
325
|
+
self.notify(event)
|
|
326
|
+
return self
|
|
327
|
+
|
|
328
|
+
def stop_using(self, module: Module):
|
|
329
|
+
"""
|
|
330
|
+
Function to remove a module in use.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
self.__modules.pop(module)
|
|
335
|
+
except KeyError:
|
|
336
|
+
...
|
|
337
|
+
else:
|
|
338
|
+
module.remove_listener(self)
|
|
339
|
+
event = ModuleRemoved(self, module)
|
|
340
|
+
self.notify(event)
|
|
341
|
+
|
|
342
|
+
return self
|
|
343
|
+
|
|
344
|
+
@contextmanager
|
|
345
|
+
def use_temporarily(
|
|
346
|
+
self,
|
|
347
|
+
module: Module,
|
|
348
|
+
priority: ModulePriorities = ModulePriorities.LOW,
|
|
349
|
+
) -> ContextDecorator:
|
|
350
|
+
"""
|
|
351
|
+
Context manager or decorator for temporary use of a module.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
self.use(module, priority)
|
|
355
|
+
yield
|
|
356
|
+
self.stop_using(module)
|
|
357
|
+
|
|
358
|
+
def change_priority(self, module: Module, priority: ModulePriorities):
|
|
359
|
+
"""
|
|
360
|
+
Function for changing the priority of a module in use.
|
|
361
|
+
There are two priority values:
|
|
362
|
+
|
|
363
|
+
* **LOW**: The module concerned becomes the least important of the modules used.
|
|
364
|
+
* **HIGH**: The module concerned becomes the most important of the modules used.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
self.__move_module(module, priority)
|
|
368
|
+
event = ModulePriorityUpdated(self, module, priority)
|
|
369
|
+
self.notify(event)
|
|
370
|
+
return self
|
|
371
|
+
|
|
372
|
+
def add_listener(self, listener: EventListener):
|
|
373
|
+
self.__channel.add_listener(listener)
|
|
374
|
+
return self
|
|
375
|
+
|
|
376
|
+
def remove_listener(self, listener: EventListener):
|
|
377
|
+
self.__channel.remove_listener(listener)
|
|
378
|
+
return self
|
|
379
|
+
|
|
380
|
+
def on_event(self, event: Event, /):
|
|
381
|
+
self_event = ModuleEventProxy(self, event)
|
|
382
|
+
|
|
383
|
+
if self_event.is_circular:
|
|
384
|
+
raise ModuleError(
|
|
385
|
+
"Circular dependency between two modules: "
|
|
386
|
+
f"`{self_event.previous_module}` and `{self}`."
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
self.notify(self_event)
|
|
390
|
+
|
|
391
|
+
def notify(self, event: Event):
|
|
392
|
+
_logger.debug(f"{event}")
|
|
393
|
+
self.__channel.dispatch(event)
|
|
394
|
+
return self
|
|
395
|
+
|
|
396
|
+
def __move_module(self, module: Module, priority: ModulePriorities):
|
|
397
|
+
last = priority == ModulePriorities.LOW
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
self.__modules.move_to_end(module, last=last)
|
|
401
|
+
except KeyError as exc:
|
|
402
|
+
raise ModuleError(
|
|
403
|
+
f"`{module}` can't be found in the modules used by `{self}`."
|
|
404
|
+
) from exc
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
Binder
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
413
|
+
class Dependencies:
|
|
414
|
+
__mapping: MappingProxyType[str, Injectable]
|
|
415
|
+
|
|
416
|
+
def __bool__(self) -> bool:
|
|
417
|
+
return bool(self.__mapping)
|
|
418
|
+
|
|
419
|
+
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
|
420
|
+
for name, injectable in self.__mapping.items():
|
|
421
|
+
yield name, injectable.get_instance()
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def arguments(self) -> Mapping[str, Any]:
|
|
425
|
+
return dict(self)
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def from_mapping(cls, mapping: Mapping[str, Injectable]):
|
|
429
|
+
return cls(MappingProxyType(mapping))
|
|
430
|
+
|
|
431
|
+
@classmethod
|
|
432
|
+
def empty(cls):
|
|
433
|
+
return cls.from_mapping({})
|
|
434
|
+
|
|
435
|
+
@classmethod
|
|
436
|
+
def resolve(cls, signature: Signature, module: Module):
|
|
437
|
+
dependencies = LazyMapping(cls.__resolver(signature, module))
|
|
438
|
+
return cls.from_mapping(dependencies)
|
|
439
|
+
|
|
440
|
+
@classmethod
|
|
441
|
+
def __resolver(
|
|
442
|
+
cls,
|
|
443
|
+
signature: Signature,
|
|
444
|
+
module: Module,
|
|
445
|
+
) -> Iterator[tuple[str, Injectable]]:
|
|
446
|
+
for name, parameter in signature.parameters.items():
|
|
447
|
+
try:
|
|
448
|
+
injectable = module[parameter.annotation]
|
|
449
|
+
except KeyError:
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
yield name, injectable
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class Arguments(NamedTuple):
|
|
456
|
+
args: Iterable[Any]
|
|
457
|
+
kwargs: Mapping[str, Any]
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class Binder(EventListener):
|
|
461
|
+
__slots__ = ("__signature", "__dependencies")
|
|
462
|
+
|
|
463
|
+
def __init__(self, signature: Signature):
|
|
464
|
+
self.__signature = signature
|
|
465
|
+
self.__dependencies = Dependencies.empty()
|
|
466
|
+
|
|
467
|
+
def bind(self, /, *args, **kwargs) -> Arguments:
|
|
468
|
+
if not self.__dependencies:
|
|
469
|
+
return Arguments(args, kwargs)
|
|
470
|
+
|
|
471
|
+
bound = self.__signature.bind_partial(*args, **kwargs)
|
|
472
|
+
bound.arguments = self.__dependencies.arguments | bound.arguments
|
|
473
|
+
return Arguments(bound.args, bound.kwargs)
|
|
474
|
+
|
|
475
|
+
def update(self, module: Module):
|
|
476
|
+
self.__dependencies = Dependencies.resolve(self.__signature, module)
|
|
477
|
+
return self
|
|
478
|
+
|
|
479
|
+
@singledispatchmethod
|
|
480
|
+
def on_event(self, event: Event, /):
|
|
481
|
+
...
|
|
482
|
+
|
|
483
|
+
@on_event.register
|
|
484
|
+
def _(self, event: ModuleEvent, /):
|
|
485
|
+
self.update(event.on_module)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
"""
|
|
489
|
+
Decorators
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@final
|
|
494
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
495
|
+
class InjectDecorator:
|
|
496
|
+
__module: Module
|
|
497
|
+
|
|
498
|
+
def __call__(self, wrapped: Callable[..., Any] = None, /):
|
|
499
|
+
def decorator(wp):
|
|
500
|
+
if isinstance(wp, type):
|
|
501
|
+
return self.__class_decorator(wp)
|
|
502
|
+
|
|
503
|
+
return self.__decorator(wp)
|
|
504
|
+
|
|
505
|
+
return decorator(wrapped) if wrapped else decorator
|
|
506
|
+
|
|
507
|
+
def __decorator(self, function: Callable[..., Any], /) -> Callable[..., Any]:
|
|
508
|
+
signature = inspect.signature(function, eval_str=True)
|
|
509
|
+
binder = Binder(signature).update(self.__module)
|
|
510
|
+
self.__module.add_listener(binder)
|
|
511
|
+
|
|
512
|
+
@wraps(function)
|
|
513
|
+
def wrapper(*args, **kwargs):
|
|
514
|
+
arguments = binder.bind(*args, **kwargs)
|
|
515
|
+
return function(*arguments.args, **arguments.kwargs)
|
|
516
|
+
|
|
517
|
+
return wrapper
|
|
518
|
+
|
|
519
|
+
def __class_decorator(self, cls: type, /) -> type:
|
|
520
|
+
init_function = type.__getattribute__(cls, "__init__")
|
|
521
|
+
type.__setattr__(cls, "__init__", self.__decorator(init_function))
|
|
522
|
+
return cls
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@final
|
|
526
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
527
|
+
class InjectableDecorator:
|
|
528
|
+
__module: Module
|
|
529
|
+
__class: type[_BaseInjectable]
|
|
530
|
+
|
|
531
|
+
def __repr__(self) -> str:
|
|
532
|
+
return f"<{self.__class.__qualname__} decorator>"
|
|
533
|
+
|
|
534
|
+
def __call__(
|
|
535
|
+
self,
|
|
536
|
+
wrapped: Callable[..., Any] = None,
|
|
537
|
+
/,
|
|
538
|
+
on: type | Iterable[type] = None,
|
|
539
|
+
auto_inject: bool = True,
|
|
540
|
+
):
|
|
541
|
+
def decorator(wp):
|
|
542
|
+
if auto_inject:
|
|
543
|
+
wp = self.__module.inject(wp)
|
|
544
|
+
|
|
545
|
+
@lambda function: function()
|
|
546
|
+
def references():
|
|
547
|
+
if reference := self.__get_reference(wp):
|
|
548
|
+
yield reference
|
|
549
|
+
|
|
550
|
+
if on is None:
|
|
551
|
+
return
|
|
552
|
+
elif isinstance(on, Iterable):
|
|
553
|
+
yield from on
|
|
554
|
+
else:
|
|
555
|
+
yield on
|
|
556
|
+
|
|
557
|
+
self.__module[references] = self.__class(wp)
|
|
558
|
+
return wp
|
|
559
|
+
|
|
560
|
+
return decorator(wrapped) if wrapped else decorator
|
|
561
|
+
|
|
562
|
+
@staticmethod
|
|
563
|
+
def __get_reference(wrapped: Callable[..., Any], /) -> type | None:
|
|
564
|
+
if isinstance(wrapped, type):
|
|
565
|
+
return wrapped
|
|
566
|
+
|
|
567
|
+
elif callable(wrapped):
|
|
568
|
+
return_type = get_annotations(wrapped, eval_str=True).get("return")
|
|
569
|
+
|
|
570
|
+
if isinstance(return_type, type):
|
|
571
|
+
return return_type
|
|
572
|
+
|
|
573
|
+
return None
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-injection"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.1"
|
|
4
4
|
description = "Fast and easy dependency injection framework."
|
|
5
5
|
authors = ["remimd"]
|
|
6
6
|
keywords = ["dependencies", "inject", "injection"]
|
|
7
7
|
license = "MIT"
|
|
8
8
|
packages = [{ include = "injection" }]
|
|
9
|
-
readme = "documentation/
|
|
10
|
-
repository = "https://github.com/
|
|
9
|
+
readme = "documentation/basic-usage.md"
|
|
10
|
+
repository = "https://github.com/simplysquare/python-injection"
|
|
11
11
|
|
|
12
12
|
[tool.poetry.dependencies]
|
|
13
13
|
python = ">=3.10, <4"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from .core import Module, new_module
|
|
2
|
-
|
|
3
|
-
__all__ = (
|
|
4
|
-
"Module",
|
|
5
|
-
"get_instance",
|
|
6
|
-
"inject",
|
|
7
|
-
"injectable",
|
|
8
|
-
"new_module",
|
|
9
|
-
"singleton",
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
_default_module = new_module()
|
|
13
|
-
|
|
14
|
-
get_instance = _default_module.get_instance
|
|
15
|
-
inject = _default_module.inject
|
|
16
|
-
injectable = _default_module.injectable
|
|
17
|
-
singleton = _default_module.singleton
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from dataclasses import dataclass, field
|
|
6
|
-
from functools import singledispatchmethod, wraps
|
|
7
|
-
from inspect import Parameter, Signature
|
|
8
|
-
from types import MappingProxyType
|
|
9
|
-
from typing import (
|
|
10
|
-
Any,
|
|
11
|
-
Callable,
|
|
12
|
-
Generic,
|
|
13
|
-
Iterable,
|
|
14
|
-
Iterator,
|
|
15
|
-
Mapping,
|
|
16
|
-
NamedTuple,
|
|
17
|
-
Protocol,
|
|
18
|
-
TypeVar,
|
|
19
|
-
cast,
|
|
20
|
-
final,
|
|
21
|
-
get_origin,
|
|
22
|
-
runtime_checkable,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
from injection.common.event import Event, EventChannel, EventListener
|
|
26
|
-
from injection.common.lazy import LazyMapping
|
|
27
|
-
from injection.exceptions import NoInjectable
|
|
28
|
-
|
|
29
|
-
__all__ = ("Module", "new_module")
|
|
30
|
-
|
|
31
|
-
T = TypeVar("T")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
35
|
-
class Injectable(Generic[T], ABC):
|
|
36
|
-
factory: Callable[[], T]
|
|
37
|
-
|
|
38
|
-
@abstractmethod
|
|
39
|
-
def get_instance(self) -> T:
|
|
40
|
-
raise NotImplementedError
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class NewInjectable(Injectable[T]):
|
|
44
|
-
__slots__ = ()
|
|
45
|
-
|
|
46
|
-
def get_instance(self) -> T:
|
|
47
|
-
return self.factory()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class SingletonInjectable(Injectable[T]):
|
|
51
|
-
__instance_attribute: str = "_instance"
|
|
52
|
-
|
|
53
|
-
__slots__ = (__instance_attribute,)
|
|
54
|
-
|
|
55
|
-
def get_instance(self) -> T:
|
|
56
|
-
cls = type(self)
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
instance = getattr(self, cls.__instance_attribute)
|
|
60
|
-
except AttributeError:
|
|
61
|
-
instance = self.factory()
|
|
62
|
-
object.__setattr__(self, cls.__instance_attribute, instance)
|
|
63
|
-
|
|
64
|
-
return instance
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
68
|
-
class ContainerUpdated(Event):
|
|
69
|
-
container: Container
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
73
|
-
class Container:
|
|
74
|
-
__data: dict[type, Injectable] = field(default_factory=dict, init=False)
|
|
75
|
-
__channel: EventChannel = field(default_factory=EventChannel, init=False)
|
|
76
|
-
|
|
77
|
-
def __getitem__(self, reference: type) -> Injectable:
|
|
78
|
-
cls = origin if (origin := get_origin(reference)) else reference
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
return self.__data[cls]
|
|
82
|
-
except KeyError as exc:
|
|
83
|
-
try:
|
|
84
|
-
name = f"{cls.__module__}.{cls.__qualname__}"
|
|
85
|
-
except AttributeError:
|
|
86
|
-
name = repr(reference)
|
|
87
|
-
|
|
88
|
-
raise NoInjectable(f"No injectable for `{name}`.") from exc
|
|
89
|
-
|
|
90
|
-
@property
|
|
91
|
-
def inject(self) -> InjectDecorator:
|
|
92
|
-
return InjectDecorator(self)
|
|
93
|
-
|
|
94
|
-
@property
|
|
95
|
-
def injectable(self) -> InjectableDecorator:
|
|
96
|
-
return InjectableDecorator(self, NewInjectable)
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def singleton(self) -> InjectableDecorator:
|
|
100
|
-
return InjectableDecorator(self, SingletonInjectable)
|
|
101
|
-
|
|
102
|
-
def set_multiple(self, references: Iterable[type], injectable: Injectable):
|
|
103
|
-
new_values = (
|
|
104
|
-
(self.check_if_exists(reference), injectable) for reference in references
|
|
105
|
-
)
|
|
106
|
-
self.__data.update(new_values)
|
|
107
|
-
self.__notify()
|
|
108
|
-
return self
|
|
109
|
-
|
|
110
|
-
def check_if_exists(self, reference: type) -> type:
|
|
111
|
-
if reference in self.__data:
|
|
112
|
-
raise RuntimeError(
|
|
113
|
-
"An injectable already exists for the "
|
|
114
|
-
f"reference class `{reference.__name__}`."
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
return reference
|
|
118
|
-
|
|
119
|
-
def add_listener(self, listener: EventListener):
|
|
120
|
-
self.__channel.add_listener(listener)
|
|
121
|
-
return self
|
|
122
|
-
|
|
123
|
-
def __notify(self):
|
|
124
|
-
event = ContainerUpdated(self)
|
|
125
|
-
self.__channel.dispatch(event)
|
|
126
|
-
return self
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
130
|
-
class Dependencies:
|
|
131
|
-
__mapping: MappingProxyType[str, Injectable]
|
|
132
|
-
|
|
133
|
-
def __bool__(self) -> bool:
|
|
134
|
-
return bool(self.__mapping)
|
|
135
|
-
|
|
136
|
-
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
|
137
|
-
for name, injectable in self.__mapping.items():
|
|
138
|
-
yield name, injectable.get_instance()
|
|
139
|
-
|
|
140
|
-
@property
|
|
141
|
-
def arguments(self) -> Mapping[str, Any]:
|
|
142
|
-
return dict(self)
|
|
143
|
-
|
|
144
|
-
@classmethod
|
|
145
|
-
def from_mapping(cls, mapping: Mapping[str, Injectable]):
|
|
146
|
-
return cls(MappingProxyType(mapping))
|
|
147
|
-
|
|
148
|
-
@classmethod
|
|
149
|
-
def empty(cls):
|
|
150
|
-
return cls.from_mapping({})
|
|
151
|
-
|
|
152
|
-
@classmethod
|
|
153
|
-
def resolve(cls, signature: Signature, container: Container):
|
|
154
|
-
dependencies = LazyMapping(cls.__resolver(signature, container))
|
|
155
|
-
return cls.from_mapping(dependencies)
|
|
156
|
-
|
|
157
|
-
@classmethod
|
|
158
|
-
def __resolver(
|
|
159
|
-
cls,
|
|
160
|
-
signature: Signature,
|
|
161
|
-
container: Container,
|
|
162
|
-
) -> Iterator[tuple[str, Injectable]]:
|
|
163
|
-
for name, parameter in signature.parameters.items():
|
|
164
|
-
try:
|
|
165
|
-
injectable = container[parameter.annotation]
|
|
166
|
-
except NoInjectable:
|
|
167
|
-
continue
|
|
168
|
-
|
|
169
|
-
yield name, injectable
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class Arguments(NamedTuple):
|
|
173
|
-
args: Iterable[Any]
|
|
174
|
-
kwargs: Mapping[str, Any]
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
class Binder(EventListener):
|
|
178
|
-
__slots__ = ("__signature", "__dependencies")
|
|
179
|
-
|
|
180
|
-
def __init__(self, signature: Signature):
|
|
181
|
-
self.__signature = signature
|
|
182
|
-
self.__dependencies = Dependencies.empty()
|
|
183
|
-
|
|
184
|
-
def bind(self, /, *args, **kwargs) -> Arguments:
|
|
185
|
-
if not self.__dependencies:
|
|
186
|
-
return Arguments(args, kwargs)
|
|
187
|
-
|
|
188
|
-
bound = self.__signature.bind_partial(*args, **kwargs)
|
|
189
|
-
arguments = self.__dependencies.arguments | bound.arguments
|
|
190
|
-
|
|
191
|
-
positional = []
|
|
192
|
-
keywords = {}
|
|
193
|
-
|
|
194
|
-
for name, parameter in self.__signature.parameters.items():
|
|
195
|
-
try:
|
|
196
|
-
value = arguments.pop(name)
|
|
197
|
-
except KeyError:
|
|
198
|
-
continue
|
|
199
|
-
|
|
200
|
-
match parameter.kind:
|
|
201
|
-
case Parameter.POSITIONAL_ONLY:
|
|
202
|
-
positional.append(value)
|
|
203
|
-
case Parameter.VAR_POSITIONAL:
|
|
204
|
-
positional.extend(value)
|
|
205
|
-
case Parameter.VAR_KEYWORD:
|
|
206
|
-
keywords.update(value)
|
|
207
|
-
case _:
|
|
208
|
-
keywords[name] = value
|
|
209
|
-
|
|
210
|
-
return Arguments(tuple(positional), keywords)
|
|
211
|
-
|
|
212
|
-
def update(self, container: Container):
|
|
213
|
-
self.__dependencies = Dependencies.resolve(self.__signature, container)
|
|
214
|
-
return self
|
|
215
|
-
|
|
216
|
-
@singledispatchmethod
|
|
217
|
-
def on_event(self, event: Event, /):
|
|
218
|
-
... # pragma: no cover
|
|
219
|
-
|
|
220
|
-
@on_event.register
|
|
221
|
-
def _(self, event: ContainerUpdated, /):
|
|
222
|
-
self.update(event.container)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
@final
|
|
226
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
227
|
-
class InjectDecorator:
|
|
228
|
-
__container: Container
|
|
229
|
-
|
|
230
|
-
def __call__(self, wrapped=None, /):
|
|
231
|
-
def decorator(wp):
|
|
232
|
-
if isinstance(wp, type):
|
|
233
|
-
return self.__class_decorator(wp)
|
|
234
|
-
|
|
235
|
-
return self.__decorator(wp)
|
|
236
|
-
|
|
237
|
-
return decorator(wrapped) if wrapped else decorator
|
|
238
|
-
|
|
239
|
-
def __decorator(self, function: Callable[..., Any], /) -> Callable[..., Any]:
|
|
240
|
-
signature = inspect.signature(function)
|
|
241
|
-
binder = Binder(signature).update(self.__container)
|
|
242
|
-
self.__container.add_listener(binder)
|
|
243
|
-
|
|
244
|
-
@wraps(function)
|
|
245
|
-
def wrapper(*args, **kwargs):
|
|
246
|
-
arguments = binder.bind(*args, **kwargs)
|
|
247
|
-
return function(*arguments.args, **arguments.kwargs)
|
|
248
|
-
|
|
249
|
-
return wrapper
|
|
250
|
-
|
|
251
|
-
def __class_decorator(self, cls: type, /) -> type:
|
|
252
|
-
init_function = type.__getattribute__(cls, "__init__")
|
|
253
|
-
type.__setattr__(cls, "__init__", self.__decorator(init_function))
|
|
254
|
-
return cls
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
@final
|
|
258
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
259
|
-
class InjectableDecorator:
|
|
260
|
-
__container: Container
|
|
261
|
-
__class: type[Injectable]
|
|
262
|
-
|
|
263
|
-
def __repr__(self) -> str:
|
|
264
|
-
return f"<{self.__class.__name__} decorator>" # pragma: no cover
|
|
265
|
-
|
|
266
|
-
def __call__(self, wrapped=None, /, on=None, auto_inject=True):
|
|
267
|
-
def decorator(wp):
|
|
268
|
-
if auto_inject:
|
|
269
|
-
wp = self.__container.inject(wp)
|
|
270
|
-
|
|
271
|
-
@lambda fn: fn()
|
|
272
|
-
def references():
|
|
273
|
-
if isinstance(wp, type):
|
|
274
|
-
yield wp
|
|
275
|
-
|
|
276
|
-
if on is None:
|
|
277
|
-
return
|
|
278
|
-
elif isinstance(on, Iterable):
|
|
279
|
-
yield from on
|
|
280
|
-
else:
|
|
281
|
-
yield on
|
|
282
|
-
|
|
283
|
-
injectable = self.__class(wp)
|
|
284
|
-
self.__container.set_multiple(references, injectable)
|
|
285
|
-
|
|
286
|
-
return wp
|
|
287
|
-
|
|
288
|
-
return decorator(wrapped) if wrapped else decorator
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
@runtime_checkable
|
|
292
|
-
class Module(Protocol):
|
|
293
|
-
__slots__ = ()
|
|
294
|
-
|
|
295
|
-
@abstractmethod
|
|
296
|
-
def get_instance(self, *args, **kwargs):
|
|
297
|
-
raise NotImplementedError
|
|
298
|
-
|
|
299
|
-
@abstractmethod
|
|
300
|
-
def inject(self, *args, **kwargs):
|
|
301
|
-
raise NotImplementedError
|
|
302
|
-
|
|
303
|
-
@abstractmethod
|
|
304
|
-
def injectable(self, *args, **kwargs):
|
|
305
|
-
raise NotImplementedError
|
|
306
|
-
|
|
307
|
-
@abstractmethod
|
|
308
|
-
def singleton(self, *args, **kwargs):
|
|
309
|
-
raise NotImplementedError
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
|
313
|
-
class InjectionModule:
|
|
314
|
-
__container: Container = field(default_factory=Container, init=False)
|
|
315
|
-
|
|
316
|
-
@property
|
|
317
|
-
def inject(self) -> InjectDecorator:
|
|
318
|
-
return self.__container.inject
|
|
319
|
-
|
|
320
|
-
@property
|
|
321
|
-
def injectable(self) -> InjectableDecorator:
|
|
322
|
-
return self.__container.injectable
|
|
323
|
-
|
|
324
|
-
@property
|
|
325
|
-
def singleton(self) -> InjectableDecorator:
|
|
326
|
-
return self.__container.singleton
|
|
327
|
-
|
|
328
|
-
def get_instance(self, reference: type[T]) -> T:
|
|
329
|
-
instance = self.__container[reference].get_instance()
|
|
330
|
-
return cast(reference, instance)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def new_module() -> Module:
|
|
334
|
-
module = InjectionModule()
|
|
335
|
-
return cast(Module, module)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
from typing import Any, Callable, Iterable, Protocol, TypeVar, runtime_checkable
|
|
3
|
-
|
|
4
|
-
_T = TypeVar("_T")
|
|
5
|
-
|
|
6
|
-
@runtime_checkable
|
|
7
|
-
class Module(Protocol):
|
|
8
|
-
"""
|
|
9
|
-
Object with isolated injection environment.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
@abstractmethod
|
|
13
|
-
def get_instance(self, reference: type[_T]) -> _T:
|
|
14
|
-
"""
|
|
15
|
-
Function used to retrieve an instance associated with the type passed in parameter or raise `NoInjectable`
|
|
16
|
-
exception.
|
|
17
|
-
"""
|
|
18
|
-
@abstractmethod
|
|
19
|
-
def inject(self, wrapped: Callable[..., Any] = ..., /):
|
|
20
|
-
"""
|
|
21
|
-
Decorator applicable to a class or function. Inject function dependencies using parameter type annotations. If
|
|
22
|
-
applied to a class, the dependencies resolved will be those of the `__init__` method.
|
|
23
|
-
|
|
24
|
-
Doesn't work with type annotations resolved by `__future__` module.
|
|
25
|
-
"""
|
|
26
|
-
@abstractmethod
|
|
27
|
-
def injectable(self, *, on: type | Iterable[type] = ..., auto_inject: bool = ...):
|
|
28
|
-
"""
|
|
29
|
-
Decorator applicable to a class or function. It is used to indicate how the injectable will be constructed. At
|
|
30
|
-
injection time, a new instance will be injected each time. Automatically injects constructor dependencies, can
|
|
31
|
-
be disabled with `auto_inject=False`.
|
|
32
|
-
"""
|
|
33
|
-
@abstractmethod
|
|
34
|
-
def singleton(self, *, on: type | Iterable[type] = ..., auto_inject: bool = ...):
|
|
35
|
-
"""
|
|
36
|
-
Decorator applicable to a class or function. It is used to indicate how the singleton will be constructed. At
|
|
37
|
-
injection time, the injected instance will always be the same. Automatically injects constructor dependencies,
|
|
38
|
-
can be disabled with `auto_inject=False`.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def new_module() -> Module:
|
|
42
|
-
"""
|
|
43
|
-
Function to create a new injection module.
|
|
44
|
-
"""
|
|
File without changes
|
|
File without changes
|