python-injection 0.7.3__tar.gz → 0.7.5__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.7.3 → python_injection-0.7.5}/PKG-INFO +1 -1
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/_pkg.py +2 -2
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/_pkg.pyi +6 -21
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/common/event.py +3 -1
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/common/lazy.py +27 -23
- python_injection-0.7.5/injection/common/tools/threading.py +28 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/core/module.py +162 -95
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/exceptions.py +5 -5
- python_injection-0.7.5/injection/integrations/__init__.py +0 -0
- python_injection-0.7.5/pyproject.toml +56 -0
- python_injection-0.7.3/injection/common/tools/__init__.py +0 -1
- python_injection-0.7.3/pyproject.toml +0 -27
- {python_injection-0.7.3 → python_injection-0.7.5}/documentation/basic-usage.md +0 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/__init__.py +0 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/common/__init__.py +0 -0
- {python_injection-0.7.3/injection/integrations → python_injection-0.7.5/injection/common/tools}/__init__.py +0 -0
- /python_injection-0.7.3/injection/common/tools/_type.py → /python_injection-0.7.5/injection/common/tools/type.py +0 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/core/__init__.py +0 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/integrations/blacksheep.py +0 -0
- {python_injection-0.7.3 → python_injection-0.7.5}/injection/utils.py +0 -0
|
@@ -39,19 +39,11 @@ class Module:
|
|
|
39
39
|
|
|
40
40
|
def __init__(self, name: str = ...): ...
|
|
41
41
|
def __contains__(self, cls: type | UnionType, /) -> bool: ...
|
|
42
|
-
def inject(
|
|
43
|
-
self,
|
|
44
|
-
wrapped: Callable[..., Any] = ...,
|
|
45
|
-
/,
|
|
46
|
-
*,
|
|
47
|
-
force: bool = ...,
|
|
48
|
-
):
|
|
42
|
+
def inject(self, wrapped: Callable[..., Any] = ..., /):
|
|
49
43
|
"""
|
|
50
44
|
Decorator applicable to a class or function. Inject function dependencies using
|
|
51
45
|
parameter type annotations. If applied to a class, the dependencies resolved
|
|
52
46
|
will be those of the `__init__` method.
|
|
53
|
-
|
|
54
|
-
With `force=True`, parameters passed to replace dependencies will be ignored.
|
|
55
47
|
"""
|
|
56
48
|
|
|
57
49
|
def injectable(
|
|
@@ -85,14 +77,7 @@ class Module:
|
|
|
85
77
|
always be the same.
|
|
86
78
|
"""
|
|
87
79
|
|
|
88
|
-
def should_be_injectable(
|
|
89
|
-
self,
|
|
90
|
-
wrapped: Callable[..., Any] = ...,
|
|
91
|
-
/,
|
|
92
|
-
*,
|
|
93
|
-
on: type | Iterable[type] | UnionType = ...,
|
|
94
|
-
override: bool = ...,
|
|
95
|
-
):
|
|
80
|
+
def should_be_injectable(self, wrapped: type = ..., /):
|
|
96
81
|
"""
|
|
97
82
|
Decorator applicable to a class. It is used to specify whether an injectable
|
|
98
83
|
should be registered. Raise an exception at injection time if the class isn't
|
|
@@ -128,7 +113,7 @@ class Module:
|
|
|
128
113
|
Example: instance = ~lazy_instance
|
|
129
114
|
"""
|
|
130
115
|
|
|
131
|
-
def use(self, module: Module, priority:
|
|
116
|
+
def use(self, module: Module, priority: ModulePriority = ...):
|
|
132
117
|
"""
|
|
133
118
|
Function for using another module. Using another module replaces the module's
|
|
134
119
|
dependencies with those of the module used. If the dependency is not found, it
|
|
@@ -143,13 +128,13 @@ class Module:
|
|
|
143
128
|
def use_temporarily(
|
|
144
129
|
self,
|
|
145
130
|
module: Module,
|
|
146
|
-
priority:
|
|
131
|
+
priority: ModulePriority = ...,
|
|
147
132
|
) -> ContextManager | ContextDecorator:
|
|
148
133
|
"""
|
|
149
134
|
Context manager or decorator for temporary use of a module.
|
|
150
135
|
"""
|
|
151
136
|
|
|
152
|
-
def change_priority(self, module: Module, priority:
|
|
137
|
+
def change_priority(self, module: Module, priority: ModulePriority):
|
|
153
138
|
"""
|
|
154
139
|
Function for changing the priority of a module in use.
|
|
155
140
|
There are two priority values:
|
|
@@ -164,7 +149,7 @@ class Module:
|
|
|
164
149
|
"""
|
|
165
150
|
|
|
166
151
|
@final
|
|
167
|
-
class
|
|
152
|
+
class ModulePriority(Enum):
|
|
168
153
|
HIGH = ...
|
|
169
154
|
LOW = ...
|
|
170
155
|
|
|
@@ -4,6 +4,8 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import ContextManager
|
|
5
5
|
from weakref import WeakSet
|
|
6
6
|
|
|
7
|
+
from injection.common.tools.threading import frozen_collection
|
|
8
|
+
|
|
7
9
|
__all__ = ("Event", "EventChannel", "EventListener")
|
|
8
10
|
|
|
9
11
|
|
|
@@ -26,7 +28,7 @@ class EventChannel:
|
|
|
26
28
|
@contextmanager
|
|
27
29
|
def dispatch(self, event: Event) -> ContextManager | ContextDecorator:
|
|
28
30
|
with ExitStack() as stack:
|
|
29
|
-
for listener in
|
|
31
|
+
for listener in frozen_collection(self.__listeners):
|
|
30
32
|
context_manager = listener.on_event(event)
|
|
31
33
|
|
|
32
34
|
if context_manager is None:
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from collections.abc import Callable, Iterator, Mapping
|
|
2
|
-
from
|
|
2
|
+
from contextlib import suppress
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
from typing import Any, Generic, TypeVar
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
from injection.common.tools.threading import thread_lock
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
_thread_lock = RLock()
|
|
8
|
+
__all__ = ("Lazy", "LazyMapping")
|
|
10
9
|
|
|
11
10
|
_T = TypeVar("_T")
|
|
12
11
|
_K = TypeVar("_K")
|
|
@@ -14,39 +13,40 @@ _V = TypeVar("_V")
|
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class Lazy(Generic[_T]):
|
|
17
|
-
__slots__ = ("
|
|
16
|
+
__slots__ = ("is_set", "__generator")
|
|
18
17
|
|
|
19
18
|
def __init__(self, factory: Callable[[], _T]):
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
def generator() -> Iterator[_T]:
|
|
20
|
+
nonlocal factory
|
|
21
|
+
|
|
22
|
+
with thread_lock:
|
|
23
|
+
value = factory()
|
|
24
|
+
self.is_set = True
|
|
25
|
+
del factory
|
|
26
|
+
|
|
27
|
+
while True:
|
|
28
|
+
yield value
|
|
29
|
+
|
|
30
|
+
self.is_set = False
|
|
31
|
+
self.__generator = generator()
|
|
22
32
|
|
|
23
33
|
def __invert__(self) -> _T:
|
|
24
|
-
|
|
25
|
-
with _thread_lock:
|
|
26
|
-
self.__value = self.__factory()
|
|
27
|
-
self.__factory = _sentinel
|
|
34
|
+
return next(self.__generator)
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
def __call__(self) -> _T:
|
|
37
|
+
return ~self
|
|
30
38
|
|
|
31
39
|
def __setattr__(self, name: str, value: Any, /):
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
with suppress(AttributeError):
|
|
41
|
+
if self.is_set:
|
|
42
|
+
raise TypeError(f"`{self}` is frozen.")
|
|
34
43
|
|
|
35
44
|
return super().__setattr__(name, value)
|
|
36
45
|
|
|
37
|
-
@property
|
|
38
|
-
def is_set(self) -> bool:
|
|
39
|
-
try:
|
|
40
|
-
return self.__factory is _sentinel
|
|
41
|
-
except AttributeError:
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
46
|
|
|
45
47
|
class LazyMapping(Mapping[_K, _V]):
|
|
46
48
|
__slots__ = ("__lazy",)
|
|
47
49
|
|
|
48
|
-
__lazy: Lazy[MappingProxyType[_K, _V]]
|
|
49
|
-
|
|
50
50
|
def __init__(self, iterator: Iterator[tuple[_K, _V]]):
|
|
51
51
|
self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
|
|
52
52
|
|
|
@@ -58,3 +58,7 @@ class LazyMapping(Mapping[_K, _V]):
|
|
|
58
58
|
|
|
59
59
|
def __len__(self) -> int:
|
|
60
60
|
return len(~self.__lazy)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def is_set(self) -> bool:
|
|
64
|
+
return self.__lazy.is_set
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from collections.abc import Callable, Collection, Iterator
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from threading import RLock
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
__all__ = ("frozen_collection", "synchronized", "thread_lock")
|
|
7
|
+
|
|
8
|
+
_T = TypeVar("_T")
|
|
9
|
+
thread_lock = RLock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def synchronized(function: Callable[..., Any] = None, /):
|
|
13
|
+
def decorator(fn):
|
|
14
|
+
@wraps(fn)
|
|
15
|
+
def wrapper(*args, **kwargs):
|
|
16
|
+
with thread_lock:
|
|
17
|
+
return fn(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
return wrapper
|
|
20
|
+
|
|
21
|
+
return decorator(function) if function else decorator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def frozen_collection(collection: Collection[_T]) -> Iterator[_T]:
|
|
25
|
+
with thread_lock:
|
|
26
|
+
t = tuple(collection)
|
|
27
|
+
|
|
28
|
+
yield from t
|
|
@@ -11,16 +11,22 @@ from collections.abc import (
|
|
|
11
11
|
Iterator,
|
|
12
12
|
Mapping,
|
|
13
13
|
MutableMapping,
|
|
14
|
+
Set,
|
|
14
15
|
)
|
|
15
16
|
from contextlib import ContextDecorator, contextmanager, suppress
|
|
16
17
|
from dataclasses import dataclass, field
|
|
17
18
|
from enum import Enum, auto
|
|
18
|
-
from functools import
|
|
19
|
+
from functools import (
|
|
20
|
+
partialmethod,
|
|
21
|
+
singledispatchmethod,
|
|
22
|
+
update_wrapper,
|
|
23
|
+
wraps,
|
|
24
|
+
)
|
|
19
25
|
from inspect import Signature, isclass
|
|
20
|
-
from
|
|
21
|
-
from types import MappingProxyType, UnionType
|
|
26
|
+
from types import UnionType
|
|
22
27
|
from typing import (
|
|
23
28
|
Any,
|
|
29
|
+
ClassVar,
|
|
24
30
|
ContextManager,
|
|
25
31
|
NamedTuple,
|
|
26
32
|
NoReturn,
|
|
@@ -32,7 +38,12 @@ from typing import (
|
|
|
32
38
|
|
|
33
39
|
from injection.common.event import Event, EventChannel, EventListener
|
|
34
40
|
from injection.common.lazy import Lazy, LazyMapping
|
|
35
|
-
from injection.common.tools import
|
|
41
|
+
from injection.common.tools.threading import (
|
|
42
|
+
frozen_collection,
|
|
43
|
+
synchronized,
|
|
44
|
+
thread_lock,
|
|
45
|
+
)
|
|
46
|
+
from injection.common.tools.type import find_types, format_type, get_origins
|
|
36
47
|
from injection.exceptions import (
|
|
37
48
|
InjectionError,
|
|
38
49
|
ModuleError,
|
|
@@ -41,10 +52,9 @@ from injection.exceptions import (
|
|
|
41
52
|
NoInjectable,
|
|
42
53
|
)
|
|
43
54
|
|
|
44
|
-
__all__ = ("Injectable", "Module", "
|
|
55
|
+
__all__ = ("Injectable", "Module", "ModulePriority")
|
|
45
56
|
|
|
46
57
|
_logger = logging.getLogger(__name__)
|
|
47
|
-
_thread_lock = RLock()
|
|
48
58
|
|
|
49
59
|
_T = TypeVar("_T")
|
|
50
60
|
Types = Iterable[type] | UnionType
|
|
@@ -117,7 +127,7 @@ class ModuleRemoved(ModuleEvent):
|
|
|
117
127
|
@dataclass(frozen=True, slots=True)
|
|
118
128
|
class ModulePriorityUpdated(ModuleEvent):
|
|
119
129
|
module_updated: Module
|
|
120
|
-
priority:
|
|
130
|
+
priority: ModulePriority
|
|
121
131
|
|
|
122
132
|
def __str__(self) -> str:
|
|
123
133
|
return (
|
|
@@ -135,13 +145,15 @@ Injectables
|
|
|
135
145
|
class Injectable(Protocol[_T]):
|
|
136
146
|
__slots__ = ()
|
|
137
147
|
|
|
138
|
-
def __init__(self, __factory: Callable[[], _T] =
|
|
148
|
+
def __init__(self, __factory: Callable[[], _T] = None, /):
|
|
149
|
+
pass
|
|
139
150
|
|
|
140
151
|
@property
|
|
141
152
|
def is_locked(self) -> bool:
|
|
142
153
|
return False
|
|
143
154
|
|
|
144
|
-
def unlock(self):
|
|
155
|
+
def unlock(self):
|
|
156
|
+
pass
|
|
145
157
|
|
|
146
158
|
@abstractmethod
|
|
147
159
|
def get_instance(self) -> _T:
|
|
@@ -163,7 +175,7 @@ class NewInjectable(BaseInjectable[_T]):
|
|
|
163
175
|
class SingletonInjectable(BaseInjectable[_T]):
|
|
164
176
|
__slots__ = ("__dict__",)
|
|
165
177
|
|
|
166
|
-
__INSTANCE_KEY = "$instance"
|
|
178
|
+
__INSTANCE_KEY: ClassVar[str] = "$instance"
|
|
167
179
|
|
|
168
180
|
@property
|
|
169
181
|
def cache(self) -> MutableMapping[str, Any]:
|
|
@@ -180,38 +192,22 @@ class SingletonInjectable(BaseInjectable[_T]):
|
|
|
180
192
|
with suppress(KeyError):
|
|
181
193
|
return self.cache[self.__INSTANCE_KEY]
|
|
182
194
|
|
|
183
|
-
with
|
|
195
|
+
with thread_lock:
|
|
184
196
|
instance = self.factory()
|
|
185
197
|
self.cache[self.__INSTANCE_KEY] = instance
|
|
186
198
|
|
|
187
199
|
return instance
|
|
188
200
|
|
|
189
201
|
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
203
|
+
class ShouldBeInjectable(Injectable[_T]):
|
|
204
|
+
cls: type[_T]
|
|
192
205
|
|
|
193
206
|
def __bool__(self) -> bool:
|
|
194
207
|
return False
|
|
195
208
|
|
|
196
|
-
@property
|
|
197
|
-
def formatted_type(self) -> str:
|
|
198
|
-
return format_type(self.factory)
|
|
199
|
-
|
|
200
|
-
@property
|
|
201
|
-
@abstractmethod
|
|
202
|
-
def exception(self) -> BaseException:
|
|
203
|
-
raise NotImplementedError
|
|
204
|
-
|
|
205
209
|
def get_instance(self) -> NoReturn:
|
|
206
|
-
raise self.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
class ShouldBeInjectable(InjectableWarning[_T]):
|
|
210
|
-
__slots__ = ()
|
|
211
|
-
|
|
212
|
-
@property
|
|
213
|
-
def exception(self) -> BaseException:
|
|
214
|
-
return InjectionError(f"`{self.formatted_type}` should be an injectable.")
|
|
210
|
+
raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
|
|
215
211
|
|
|
216
212
|
|
|
217
213
|
"""
|
|
@@ -265,26 +261,34 @@ class Container(Broker):
|
|
|
265
261
|
def is_locked(self) -> bool:
|
|
266
262
|
return any(injectable.is_locked for injectable in self.__injectables)
|
|
267
263
|
|
|
264
|
+
@property
|
|
265
|
+
def __classes(self) -> frozenset[type]:
|
|
266
|
+
return frozenset(self.__data.keys())
|
|
267
|
+
|
|
268
268
|
@property
|
|
269
269
|
def __injectables(self) -> frozenset[Injectable]:
|
|
270
270
|
return frozenset(self.__data.values())
|
|
271
271
|
|
|
272
|
-
def update(self, classes:
|
|
273
|
-
|
|
274
|
-
{origin: injectable for origin in get_origins(*classes)}
|
|
275
|
-
)
|
|
272
|
+
def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
|
|
273
|
+
classes = frozenset(get_origins(*classes))
|
|
276
274
|
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
with thread_lock:
|
|
276
|
+
if not injectable:
|
|
277
|
+
classes -= self.__classes
|
|
278
|
+
override = True
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
if classes:
|
|
281
|
+
event = ContainerDependenciesUpdated(self, classes, override)
|
|
282
|
+
|
|
283
|
+
with self.notify(event):
|
|
284
|
+
if not override:
|
|
285
|
+
self.__check_if_exists(classes)
|
|
283
286
|
|
|
284
|
-
|
|
287
|
+
self.__data.update((cls, injectable) for cls in classes)
|
|
285
288
|
|
|
286
289
|
return self
|
|
287
290
|
|
|
291
|
+
@synchronized
|
|
288
292
|
def unlock(self):
|
|
289
293
|
for injectable in self.__injectables:
|
|
290
294
|
injectable.unlock()
|
|
@@ -296,9 +300,11 @@ class Container(Broker):
|
|
|
296
300
|
def notify(self, event: Event) -> ContextManager | ContextDecorator:
|
|
297
301
|
return self.__channel.dispatch(event)
|
|
298
302
|
|
|
299
|
-
def __check_if_exists(self,
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
def __check_if_exists(self, classes: Set[type]):
|
|
304
|
+
intersection = classes & self.__classes
|
|
305
|
+
|
|
306
|
+
for cls in intersection:
|
|
307
|
+
if self.__data[cls]:
|
|
302
308
|
raise RuntimeError(
|
|
303
309
|
f"An injectable already exists for the class `{format_type(cls)}`."
|
|
304
310
|
)
|
|
@@ -309,7 +315,7 @@ Module
|
|
|
309
315
|
"""
|
|
310
316
|
|
|
311
317
|
|
|
312
|
-
class
|
|
318
|
+
class ModulePriority(Enum):
|
|
313
319
|
HIGH = auto()
|
|
314
320
|
LOW = auto()
|
|
315
321
|
|
|
@@ -339,7 +345,7 @@ class Module(EventListener, Broker):
|
|
|
339
345
|
raise NoInjectable(cls)
|
|
340
346
|
|
|
341
347
|
def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
|
|
342
|
-
self.update((cls,), injectable
|
|
348
|
+
self.update((cls,), injectable)
|
|
343
349
|
|
|
344
350
|
def __contains__(self, cls: type | UnionType, /) -> bool:
|
|
345
351
|
return any(cls in broker for broker in self.__brokers)
|
|
@@ -353,7 +359,7 @@ class Module(EventListener, Broker):
|
|
|
353
359
|
|
|
354
360
|
@property
|
|
355
361
|
def __brokers(self) -> Iterator[Broker]:
|
|
356
|
-
yield from
|
|
362
|
+
yield from frozen_collection(self.__modules)
|
|
357
363
|
yield self.__container
|
|
358
364
|
|
|
359
365
|
def injectable(
|
|
@@ -376,11 +382,13 @@ class Module(EventListener, Broker):
|
|
|
376
382
|
return decorator(wrapped) if wrapped else decorator
|
|
377
383
|
|
|
378
384
|
singleton = partialmethod(injectable, cls=SingletonInjectable)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
385
|
+
|
|
386
|
+
def should_be_injectable(self, wrapped: type = None, /):
|
|
387
|
+
def decorator(wp):
|
|
388
|
+
self[wp] = ShouldBeInjectable(wp)
|
|
389
|
+
return wp
|
|
390
|
+
|
|
391
|
+
return decorator(wrapped) if wrapped else decorator
|
|
384
392
|
|
|
385
393
|
def set_constant(
|
|
386
394
|
self,
|
|
@@ -403,21 +411,15 @@ class Module(EventListener, Broker):
|
|
|
403
411
|
wrapped: Callable[..., Any] = None,
|
|
404
412
|
/,
|
|
405
413
|
*,
|
|
406
|
-
force: bool = False,
|
|
407
414
|
return_factory: bool = False,
|
|
408
415
|
):
|
|
409
416
|
def decorator(wp):
|
|
410
417
|
if not return_factory and isclass(wp):
|
|
411
|
-
wp.__init__ = self.inject(wp.__init__
|
|
418
|
+
wp.__init__ = self.inject(wp.__init__)
|
|
412
419
|
return wp
|
|
413
420
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
@wraps(wp)
|
|
417
|
-
def wrapper(*args, **kwargs):
|
|
418
|
-
arguments = (~lazy_binder).bind(args, kwargs, force)
|
|
419
|
-
return wp(*arguments.args, **arguments.kwargs)
|
|
420
|
-
|
|
421
|
+
wrapper = InjectedFunction(wp).update(self)
|
|
422
|
+
self.add_listener(wrapper)
|
|
421
423
|
return wrapper
|
|
422
424
|
|
|
423
425
|
return decorator(wrapped) if wrapped else decorator
|
|
@@ -437,14 +439,19 @@ class Module(EventListener, Broker):
|
|
|
437
439
|
def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
|
|
438
440
|
return Lazy(lambda: self.get_instance(cls))
|
|
439
441
|
|
|
440
|
-
def update(
|
|
442
|
+
def update(
|
|
443
|
+
self,
|
|
444
|
+
classes: Iterable[type],
|
|
445
|
+
injectable: Injectable,
|
|
446
|
+
override: bool = False,
|
|
447
|
+
):
|
|
441
448
|
self.__container.update(classes, injectable, override)
|
|
442
449
|
return self
|
|
443
450
|
|
|
444
451
|
def use(
|
|
445
452
|
self,
|
|
446
453
|
module: Module,
|
|
447
|
-
priority:
|
|
454
|
+
priority: ModulePriority = ModulePriority.get_default(),
|
|
448
455
|
):
|
|
449
456
|
if module is self:
|
|
450
457
|
raise ModuleError("Module can't be used by itself.")
|
|
@@ -475,13 +482,13 @@ class Module(EventListener, Broker):
|
|
|
475
482
|
def use_temporarily(
|
|
476
483
|
self,
|
|
477
484
|
module: Module,
|
|
478
|
-
priority:
|
|
485
|
+
priority: ModulePriority = ModulePriority.get_default(),
|
|
479
486
|
) -> ContextManager | ContextDecorator:
|
|
480
487
|
self.use(module, priority)
|
|
481
488
|
yield
|
|
482
489
|
self.stop_using(module)
|
|
483
490
|
|
|
484
|
-
def change_priority(self, module: Module, priority:
|
|
491
|
+
def change_priority(self, module: Module, priority: ModulePriority):
|
|
485
492
|
event = ModulePriorityUpdated(self, module, priority)
|
|
486
493
|
|
|
487
494
|
with self.notify(event):
|
|
@@ -489,6 +496,7 @@ class Module(EventListener, Broker):
|
|
|
489
496
|
|
|
490
497
|
return self
|
|
491
498
|
|
|
499
|
+
@synchronized
|
|
492
500
|
def unlock(self):
|
|
493
501
|
for broker in self.__brokers:
|
|
494
502
|
broker.unlock()
|
|
@@ -517,8 +525,8 @@ class Module(EventListener, Broker):
|
|
|
517
525
|
if self.is_locked:
|
|
518
526
|
raise ModuleLockError(f"`{self}` is locked.")
|
|
519
527
|
|
|
520
|
-
def __move_module(self, module: Module, priority:
|
|
521
|
-
last = priority ==
|
|
528
|
+
def __move_module(self, module: Module, priority: ModulePriority):
|
|
529
|
+
last = priority == ModulePriority.LOW
|
|
522
530
|
|
|
523
531
|
try:
|
|
524
532
|
self.__modules.move_to_end(module, last=last)
|
|
@@ -527,44 +535,45 @@ class Module(EventListener, Broker):
|
|
|
527
535
|
f"`{module}` can't be found in the modules used by `{self}`."
|
|
528
536
|
) from exc
|
|
529
537
|
|
|
530
|
-
def __new_binder(self, target: Callable[..., Any]) -> Binder:
|
|
531
|
-
signature = inspect.signature(target, eval_str=True)
|
|
532
|
-
binder = Binder(signature).update(self)
|
|
533
|
-
self.add_listener(binder)
|
|
534
|
-
return binder
|
|
535
|
-
|
|
536
538
|
|
|
537
539
|
"""
|
|
538
|
-
|
|
540
|
+
InjectedFunction
|
|
539
541
|
"""
|
|
540
542
|
|
|
541
543
|
|
|
542
544
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
543
545
|
class Dependencies:
|
|
544
|
-
|
|
546
|
+
mapping: Mapping[str, Injectable]
|
|
545
547
|
|
|
546
548
|
def __bool__(self) -> bool:
|
|
547
|
-
return bool(self.
|
|
549
|
+
return bool(self.mapping)
|
|
548
550
|
|
|
549
551
|
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
|
550
|
-
for name, injectable in self.
|
|
552
|
+
for name, injectable in self.mapping.items():
|
|
551
553
|
yield name, injectable.get_instance()
|
|
552
554
|
|
|
555
|
+
@property
|
|
556
|
+
def are_resolved(self) -> bool:
|
|
557
|
+
if isinstance(self.mapping, LazyMapping) and not self.mapping.is_set:
|
|
558
|
+
return False
|
|
559
|
+
|
|
560
|
+
return bool(self)
|
|
561
|
+
|
|
553
562
|
@property
|
|
554
563
|
def arguments(self) -> OrderedDict[str, Any]:
|
|
555
564
|
return OrderedDict(self)
|
|
556
565
|
|
|
557
566
|
@classmethod
|
|
558
567
|
def from_mapping(cls, mapping: Mapping[str, Injectable]):
|
|
559
|
-
return cls(
|
|
568
|
+
return cls(mapping=mapping)
|
|
560
569
|
|
|
561
570
|
@classmethod
|
|
562
571
|
def empty(cls):
|
|
563
572
|
return cls.from_mapping({})
|
|
564
573
|
|
|
565
574
|
@classmethod
|
|
566
|
-
def resolve(cls, signature: Signature, module: Module):
|
|
567
|
-
dependencies = LazyMapping(cls.__resolver(signature, module))
|
|
575
|
+
def resolve(cls, signature: Signature, module: Module, owner: type = None):
|
|
576
|
+
dependencies = LazyMapping(cls.__resolver(signature, module, owner))
|
|
568
577
|
return cls.from_mapping(dependencies)
|
|
569
578
|
|
|
570
579
|
@classmethod
|
|
@@ -572,33 +581,88 @@ class Dependencies:
|
|
|
572
581
|
cls,
|
|
573
582
|
signature: Signature,
|
|
574
583
|
module: Module,
|
|
584
|
+
owner: type = None,
|
|
575
585
|
) -> Iterator[tuple[str, Injectable]]:
|
|
576
|
-
for name,
|
|
586
|
+
for name, annotation in cls.__get_annotations(signature, owner):
|
|
577
587
|
try:
|
|
578
|
-
injectable = module[
|
|
588
|
+
injectable = module[annotation]
|
|
579
589
|
except KeyError:
|
|
580
590
|
continue
|
|
581
591
|
|
|
582
592
|
yield name, injectable
|
|
583
593
|
|
|
594
|
+
@staticmethod
|
|
595
|
+
def __get_annotations(
|
|
596
|
+
signature: Signature,
|
|
597
|
+
owner: type = None,
|
|
598
|
+
) -> Iterator[tuple[str, type | Any]]:
|
|
599
|
+
parameters = iter(signature.parameters.items())
|
|
600
|
+
|
|
601
|
+
if owner:
|
|
602
|
+
name, _ = next(parameters)
|
|
603
|
+
yield name, owner
|
|
604
|
+
|
|
605
|
+
for name, parameter in parameters:
|
|
606
|
+
yield name, parameter.annotation
|
|
607
|
+
|
|
584
608
|
|
|
585
609
|
class Arguments(NamedTuple):
|
|
586
610
|
args: Iterable[Any]
|
|
587
611
|
kwargs: Mapping[str, Any]
|
|
588
612
|
|
|
589
613
|
|
|
590
|
-
class
|
|
591
|
-
__slots__ = ("
|
|
614
|
+
class InjectedFunction(EventListener):
|
|
615
|
+
__slots__ = ("__dict__", "__wrapper", "__dependencies", "__owner")
|
|
616
|
+
|
|
617
|
+
def __init__(self, wrapped: Callable[..., Any], /):
|
|
618
|
+
update_wrapper(self, wrapped)
|
|
619
|
+
self.__signature__ = Lazy[Signature](
|
|
620
|
+
lambda: inspect.signature(wrapped, eval_str=True)
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
@wraps(wrapped)
|
|
624
|
+
def wrapper(*args, **kwargs):
|
|
625
|
+
args, kwargs = self.bind(args, kwargs)
|
|
626
|
+
return wrapped(*args, **kwargs)
|
|
592
627
|
|
|
593
|
-
|
|
594
|
-
self.__signature = signature
|
|
628
|
+
self.__wrapper = wrapper
|
|
595
629
|
self.__dependencies = Dependencies.empty()
|
|
630
|
+
self.__owner = None
|
|
631
|
+
|
|
632
|
+
def __repr__(self) -> str:
|
|
633
|
+
return repr(self.__wrapper)
|
|
634
|
+
|
|
635
|
+
def __str__(self) -> str:
|
|
636
|
+
return str(self.__wrapper)
|
|
637
|
+
|
|
638
|
+
def __call__(self, /, *args, **kwargs) -> Any:
|
|
639
|
+
return self.__wrapper(*args, **kwargs)
|
|
640
|
+
|
|
641
|
+
def __get__(self, instance: object | None, owner: type):
|
|
642
|
+
if instance is None:
|
|
643
|
+
return self
|
|
644
|
+
|
|
645
|
+
return self.__wrapper.__get__(instance, owner)
|
|
646
|
+
|
|
647
|
+
def __set_name__(self, owner: type, name: str):
|
|
648
|
+
if self.__dependencies.are_resolved:
|
|
649
|
+
raise TypeError(
|
|
650
|
+
"`__set_name__` is called after dependencies have been resolved."
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
if self.__owner:
|
|
654
|
+
raise TypeError("Function owner is already defined.")
|
|
655
|
+
|
|
656
|
+
self.__owner = owner
|
|
657
|
+
|
|
658
|
+
@property
|
|
659
|
+
def signature(self) -> Signature:
|
|
660
|
+
return self.__signature__()
|
|
596
661
|
|
|
597
662
|
def bind(
|
|
598
663
|
self,
|
|
599
664
|
args: Iterable[Any] = (),
|
|
600
665
|
kwargs: Mapping[str, Any] = None,
|
|
601
|
-
force: bool = False,
|
|
602
666
|
) -> Arguments:
|
|
603
667
|
if kwargs is None:
|
|
604
668
|
kwargs = {}
|
|
@@ -606,22 +670,25 @@ class Binder(EventListener):
|
|
|
606
670
|
if not self.__dependencies:
|
|
607
671
|
return Arguments(args, kwargs)
|
|
608
672
|
|
|
609
|
-
bound = self.
|
|
673
|
+
bound = self.signature.bind_partial(*args, **kwargs)
|
|
610
674
|
dependencies = self.__dependencies.arguments
|
|
611
|
-
|
|
612
|
-
if force:
|
|
613
|
-
bound.arguments |= dependencies
|
|
614
|
-
else:
|
|
615
|
-
bound.arguments = dependencies | bound.arguments
|
|
675
|
+
bound.arguments = dependencies | bound.arguments
|
|
616
676
|
|
|
617
677
|
return Arguments(bound.args, bound.kwargs)
|
|
618
678
|
|
|
619
679
|
def update(self, module: Module):
|
|
620
|
-
|
|
680
|
+
with thread_lock:
|
|
681
|
+
self.__dependencies = Dependencies.resolve(
|
|
682
|
+
self.signature,
|
|
683
|
+
module,
|
|
684
|
+
self.__owner,
|
|
685
|
+
)
|
|
686
|
+
|
|
621
687
|
return self
|
|
622
688
|
|
|
623
689
|
@singledispatchmethod
|
|
624
|
-
def on_event(self, event: Event, /):
|
|
690
|
+
def on_event(self, event: Event, /):
|
|
691
|
+
pass
|
|
625
692
|
|
|
626
693
|
@on_event.register
|
|
627
694
|
@contextmanager
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from injection.common.tools import format_type
|
|
1
|
+
from injection.common.tools.type import format_type
|
|
2
2
|
|
|
3
3
|
__all__ = (
|
|
4
4
|
"InjectionError",
|
|
@@ -10,7 +10,7 @@ __all__ = (
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class InjectionError(Exception):
|
|
13
|
-
|
|
13
|
+
pass
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class NoInjectable(KeyError, InjectionError):
|
|
@@ -26,12 +26,12 @@ class NoInjectable(KeyError, InjectionError):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class ModuleError(InjectionError):
|
|
29
|
-
|
|
29
|
+
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ModuleLockError(ModuleError):
|
|
33
|
-
|
|
33
|
+
pass
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class ModuleNotUsedError(KeyError, ModuleError):
|
|
37
|
-
|
|
37
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "python-injection"
|
|
3
|
+
version = "0.7.5"
|
|
4
|
+
description = "Fast and easy dependency injection framework."
|
|
5
|
+
authors = ["remimd"]
|
|
6
|
+
keywords = ["dependencies", "inject", "injection"]
|
|
7
|
+
license = "MIT"
|
|
8
|
+
packages = [{ include = "injection" }]
|
|
9
|
+
readme = "documentation/basic-usage.md"
|
|
10
|
+
repository = "https://github.com/100nm/python-injection"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = ">=3.10, <4"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.group.dev.dependencies]
|
|
16
|
+
blacksheep = "*"
|
|
17
|
+
pydantic = "*"
|
|
18
|
+
pytest = "*"
|
|
19
|
+
pytest-asyncio = "*"
|
|
20
|
+
pytest-cov = "*"
|
|
21
|
+
ruff = "*"
|
|
22
|
+
|
|
23
|
+
[tool.coverage.report]
|
|
24
|
+
exclude_lines = [
|
|
25
|
+
"pass",
|
|
26
|
+
"pragma: no cover",
|
|
27
|
+
"raise NotImplementedError",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
python_files = "test_*.py"
|
|
32
|
+
addopts = "-p no:warnings --tb=short"
|
|
33
|
+
asyncio_mode = "auto"
|
|
34
|
+
testpaths = "tests/"
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 88
|
|
38
|
+
indent-width = 4
|
|
39
|
+
|
|
40
|
+
[tool.ruff.format]
|
|
41
|
+
quote-style = "double"
|
|
42
|
+
indent-style = "space"
|
|
43
|
+
skip-magic-trailing-comma = false
|
|
44
|
+
line-ending = "auto"
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
extend-select = ["F", "I", "N"]
|
|
48
|
+
ignore = ["N818"]
|
|
49
|
+
fixable = ["ALL"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint.per-file-ignores]
|
|
52
|
+
"__init__.py" = ["F403"]
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["poetry-core"]
|
|
56
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from ._type import *
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "python-injection"
|
|
3
|
-
version = "0.7.3"
|
|
4
|
-
description = "Fast and easy dependency injection framework."
|
|
5
|
-
authors = ["remimd"]
|
|
6
|
-
keywords = ["dependencies", "inject", "injection"]
|
|
7
|
-
license = "MIT"
|
|
8
|
-
packages = [{ include = "injection" }]
|
|
9
|
-
readme = "documentation/basic-usage.md"
|
|
10
|
-
repository = "https://github.com/100nm/python-injection"
|
|
11
|
-
|
|
12
|
-
[tool.poetry.dependencies]
|
|
13
|
-
python = ">=3.10, <4"
|
|
14
|
-
|
|
15
|
-
[tool.poetry.group.dev.dependencies]
|
|
16
|
-
black = "*"
|
|
17
|
-
blacksheep = "^2.0.7"
|
|
18
|
-
flake8 = "*"
|
|
19
|
-
isort = "*"
|
|
20
|
-
pydantic = "^2.6.3"
|
|
21
|
-
pytest = "*"
|
|
22
|
-
pytest-asyncio = "*"
|
|
23
|
-
pytest-cov = "*"
|
|
24
|
-
|
|
25
|
-
[build-system]
|
|
26
|
-
requires = ["poetry-core"]
|
|
27
|
-
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|