python-injection 0.8.5__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- injection/__init__.py +10 -9
- injection/__init__.pyi +65 -46
- injection/common/event.py +3 -3
- injection/common/invertible.py +6 -8
- injection/common/lazy.py +9 -14
- injection/common/tools/threading.py +4 -4
- injection/common/tools/type.py +54 -27
- injection/core/module.py +150 -110
- injection/exceptions.py +4 -6
- injection/integrations/blacksheep.py +5 -7
- injection/testing/__init__.py +38 -0
- injection/testing/__init__.pyi +22 -0
- {python_injection-0.8.5.dist-info → python_injection-0.9.1.dist-info}/METADATA +3 -5
- python_injection-0.9.1.dist-info/RECORD +21 -0
- python_injection-0.8.5.dist-info/RECORD +0 -19
- {python_injection-0.8.5.dist-info → python_injection-0.9.1.dist-info}/WHEEL +0 -0
injection/__init__.py
CHANGED
|
@@ -7,7 +7,6 @@ __all__ = (
|
|
|
7
7
|
"InjectableMode",
|
|
8
8
|
"Module",
|
|
9
9
|
"ModulePriority",
|
|
10
|
-
"default_module",
|
|
11
10
|
"get_instance",
|
|
12
11
|
"get_lazy_instance",
|
|
13
12
|
"inject",
|
|
@@ -17,12 +16,14 @@ __all__ = (
|
|
|
17
16
|
"singleton",
|
|
18
17
|
)
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
_module = Module.default()
|
|
21
20
|
|
|
22
|
-
get_instance =
|
|
23
|
-
get_lazy_instance =
|
|
24
|
-
inject =
|
|
25
|
-
injectable =
|
|
26
|
-
set_constant =
|
|
27
|
-
should_be_injectable =
|
|
28
|
-
singleton =
|
|
21
|
+
get_instance = _module.get_instance
|
|
22
|
+
get_lazy_instance = _module.get_lazy_instance
|
|
23
|
+
inject = _module.inject
|
|
24
|
+
injectable = _module.injectable
|
|
25
|
+
set_constant = _module.set_constant
|
|
26
|
+
should_be_injectable = _module.should_be_injectable
|
|
27
|
+
singleton = _module.singleton
|
|
28
|
+
|
|
29
|
+
del _module
|
injection/__init__.pyi
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from collections.abc import Callable
|
|
2
|
+
from collections.abc import Callable
|
|
3
3
|
from contextlib import ContextDecorator
|
|
4
|
-
from enum import
|
|
4
|
+
from enum import StrEnum
|
|
5
5
|
from types import UnionType
|
|
6
6
|
from typing import (
|
|
7
7
|
Any,
|
|
8
8
|
ContextManager,
|
|
9
|
-
Final,
|
|
10
|
-
Literal,
|
|
11
9
|
Protocol,
|
|
12
|
-
|
|
10
|
+
Self,
|
|
13
11
|
final,
|
|
14
12
|
runtime_checkable,
|
|
15
13
|
)
|
|
16
14
|
|
|
17
15
|
from .common.invertible import Invertible
|
|
16
|
+
from .common.tools.type import TypeInfo
|
|
17
|
+
from .core import InjectableFactory
|
|
18
|
+
from .core import ModeStr as InjectableModeStr
|
|
19
|
+
from .core import PriorityStr as ModulePriorityStr
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
_T_co = TypeVar("_T_co", covariant=True)
|
|
21
|
+
_module: Module = ...
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
get_instance = _module.get_instance
|
|
24
|
+
get_lazy_instance = _module.get_lazy_instance
|
|
25
|
+
inject = _module.inject
|
|
26
|
+
injectable = _module.injectable
|
|
27
|
+
set_constant = _module.set_constant
|
|
28
|
+
should_be_injectable = _module.should_be_injectable
|
|
29
|
+
singleton = _module.singleton
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
get_lazy_instance = default_module.get_lazy_instance
|
|
26
|
-
inject = default_module.inject
|
|
27
|
-
injectable = default_module.injectable
|
|
28
|
-
set_constant = default_module.set_constant
|
|
29
|
-
should_be_injectable = default_module.should_be_injectable
|
|
30
|
-
singleton = default_module.singleton
|
|
31
|
+
del _module
|
|
31
32
|
|
|
32
33
|
@final
|
|
33
34
|
class Module:
|
|
@@ -41,6 +42,8 @@ class Module:
|
|
|
41
42
|
|
|
42
43
|
def __init__(self, name: str = ...): ...
|
|
43
44
|
def __contains__(self, cls: type | UnionType, /) -> bool: ...
|
|
45
|
+
@property
|
|
46
|
+
def is_locked(self) -> bool: ...
|
|
44
47
|
def inject(self, wrapped: Callable[..., Any] = ..., /):
|
|
45
48
|
"""
|
|
46
49
|
Decorator applicable to a class or function. Inject function dependencies using
|
|
@@ -48,15 +51,15 @@ class Module:
|
|
|
48
51
|
will be those of the `__init__` method.
|
|
49
52
|
"""
|
|
50
53
|
|
|
51
|
-
def injectable(
|
|
54
|
+
def injectable[T](
|
|
52
55
|
self,
|
|
53
|
-
wrapped: Callable[...,
|
|
56
|
+
wrapped: Callable[..., T] = ...,
|
|
54
57
|
/,
|
|
55
58
|
*,
|
|
56
|
-
cls:
|
|
59
|
+
cls: InjectableFactory[T] = ...,
|
|
57
60
|
inject: bool = ...,
|
|
58
|
-
on:
|
|
59
|
-
mode: InjectableMode |
|
|
61
|
+
on: TypeInfo[T] = ...,
|
|
62
|
+
mode: InjectableMode | InjectableModeStr = ...,
|
|
60
63
|
):
|
|
61
64
|
"""
|
|
62
65
|
Decorator applicable to a class or function. It is used to indicate how the
|
|
@@ -64,14 +67,14 @@ class Module:
|
|
|
64
67
|
injected each time.
|
|
65
68
|
"""
|
|
66
69
|
|
|
67
|
-
def singleton(
|
|
70
|
+
def singleton[T](
|
|
68
71
|
self,
|
|
69
|
-
wrapped: Callable[...,
|
|
72
|
+
wrapped: Callable[..., T] = ...,
|
|
70
73
|
/,
|
|
71
74
|
*,
|
|
72
75
|
inject: bool = ...,
|
|
73
|
-
on:
|
|
74
|
-
mode: InjectableMode |
|
|
76
|
+
on: TypeInfo[T] = ...,
|
|
77
|
+
mode: InjectableMode | InjectableModeStr = ...,
|
|
75
78
|
):
|
|
76
79
|
"""
|
|
77
80
|
Decorator applicable to a class or function. It is used to indicate how the
|
|
@@ -86,37 +89,37 @@ class Module:
|
|
|
86
89
|
registered.
|
|
87
90
|
"""
|
|
88
91
|
|
|
89
|
-
def set_constant(
|
|
92
|
+
def set_constant[T](
|
|
90
93
|
self,
|
|
91
|
-
instance:
|
|
92
|
-
on:
|
|
94
|
+
instance: T,
|
|
95
|
+
on: TypeInfo[T] = ...,
|
|
93
96
|
*,
|
|
94
|
-
mode: InjectableMode |
|
|
95
|
-
) ->
|
|
97
|
+
mode: InjectableMode | InjectableModeStr = ...,
|
|
98
|
+
) -> T:
|
|
96
99
|
"""
|
|
97
100
|
Function for registering a specific instance to be injected. This is useful for
|
|
98
101
|
registering global variables. The difference with the singleton decorator is
|
|
99
102
|
that no dependencies are resolved, so the module doesn't need to be locked.
|
|
100
103
|
"""
|
|
101
104
|
|
|
102
|
-
def resolve(self, cls: type[
|
|
105
|
+
def resolve[T](self, cls: type[T]) -> T:
|
|
103
106
|
"""
|
|
104
107
|
Function used to retrieve an instance associated with the type passed in
|
|
105
108
|
parameter or an exception will be raised.
|
|
106
109
|
"""
|
|
107
110
|
|
|
108
|
-
def get_instance(self, cls: type[
|
|
111
|
+
def get_instance[T](self, cls: type[T]) -> T | None:
|
|
109
112
|
"""
|
|
110
113
|
Function used to retrieve an instance associated with the type passed in
|
|
111
114
|
parameter or return `None`.
|
|
112
115
|
"""
|
|
113
116
|
|
|
114
|
-
def get_lazy_instance(
|
|
117
|
+
def get_lazy_instance[T](
|
|
115
118
|
self,
|
|
116
|
-
cls: type[
|
|
119
|
+
cls: type[T],
|
|
117
120
|
*,
|
|
118
121
|
cache: bool = ...,
|
|
119
|
-
) -> Invertible[
|
|
122
|
+
) -> Invertible[T | None]:
|
|
120
123
|
"""
|
|
121
124
|
Function used to retrieve an instance associated with the type passed in
|
|
122
125
|
parameter or `None`. Return a `Invertible` object. To access the instance
|
|
@@ -126,19 +129,24 @@ class Module:
|
|
|
126
129
|
Example: instance = ~lazy_instance
|
|
127
130
|
"""
|
|
128
131
|
|
|
132
|
+
def init_modules(self, *modules: Module) -> Self:
|
|
133
|
+
"""
|
|
134
|
+
Function to clean modules in use and to use those passed as parameters.
|
|
135
|
+
"""
|
|
136
|
+
|
|
129
137
|
def use(
|
|
130
138
|
self,
|
|
131
139
|
module: Module,
|
|
132
140
|
*,
|
|
133
|
-
priority: ModulePriority |
|
|
134
|
-
):
|
|
141
|
+
priority: ModulePriority | ModulePriorityStr = ...,
|
|
142
|
+
) -> Self:
|
|
135
143
|
"""
|
|
136
144
|
Function for using another module. Using another module replaces the module's
|
|
137
145
|
dependencies with those of the module used. If the dependency is not found, it
|
|
138
146
|
will be searched for in the module's dependency container.
|
|
139
147
|
"""
|
|
140
148
|
|
|
141
|
-
def stop_using(self, module: Module):
|
|
149
|
+
def stop_using(self, module: Module) -> Self:
|
|
142
150
|
"""
|
|
143
151
|
Function to remove a module in use.
|
|
144
152
|
"""
|
|
@@ -147,7 +155,7 @@ class Module:
|
|
|
147
155
|
self,
|
|
148
156
|
module: Module,
|
|
149
157
|
*,
|
|
150
|
-
priority: ModulePriority |
|
|
158
|
+
priority: ModulePriority | ModulePriorityStr = ...,
|
|
151
159
|
) -> ContextManager | ContextDecorator:
|
|
152
160
|
"""
|
|
153
161
|
Context manager or decorator for temporary use of a module.
|
|
@@ -156,8 +164,8 @@ class Module:
|
|
|
156
164
|
def change_priority(
|
|
157
165
|
self,
|
|
158
166
|
module: Module,
|
|
159
|
-
priority: ModulePriority |
|
|
160
|
-
):
|
|
167
|
+
priority: ModulePriority | ModulePriorityStr,
|
|
168
|
+
) -> Self:
|
|
161
169
|
"""
|
|
162
170
|
Function for changing the priority of a module in use.
|
|
163
171
|
There are two priority values:
|
|
@@ -166,27 +174,38 @@ class Module:
|
|
|
166
174
|
* **HIGH**: The module concerned becomes the most important of the modules used.
|
|
167
175
|
"""
|
|
168
176
|
|
|
169
|
-
def unlock(self):
|
|
177
|
+
def unlock(self) -> Self:
|
|
170
178
|
"""
|
|
171
179
|
Function to unlock the module by deleting cached instances of singletons.
|
|
172
180
|
"""
|
|
173
181
|
|
|
182
|
+
@classmethod
|
|
183
|
+
def from_name(cls, name: str) -> Self:
|
|
184
|
+
"""
|
|
185
|
+
Class method for getting or creating a module by name.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def default(cls) -> Self:
|
|
190
|
+
"""
|
|
191
|
+
Class method for getting the default module.
|
|
192
|
+
"""
|
|
193
|
+
|
|
174
194
|
@final
|
|
175
|
-
class ModulePriority(
|
|
195
|
+
class ModulePriority(StrEnum):
|
|
176
196
|
LOW = ...
|
|
177
197
|
HIGH = ...
|
|
178
198
|
|
|
179
199
|
@runtime_checkable
|
|
180
|
-
class Injectable(Protocol
|
|
181
|
-
def __init__(self, factory: Callable[[], _T_co] = ..., /): ...
|
|
200
|
+
class Injectable[T](Protocol):
|
|
182
201
|
@property
|
|
183
202
|
def is_locked(self) -> bool: ...
|
|
184
203
|
def unlock(self): ...
|
|
185
204
|
@abstractmethod
|
|
186
|
-
def get_instance(self) ->
|
|
205
|
+
def get_instance(self) -> T: ...
|
|
187
206
|
|
|
188
207
|
@final
|
|
189
|
-
class InjectableMode(
|
|
208
|
+
class InjectableMode(StrEnum):
|
|
190
209
|
FALLBACK = ...
|
|
191
210
|
NORMAL = ...
|
|
192
211
|
OVERRIDE = ...
|
injection/common/event.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from contextlib import ExitStack, contextmanager, suppress
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import ContextManager
|
|
4
|
+
from typing import ContextManager, Self
|
|
5
5
|
from weakref import WeakSet
|
|
6
6
|
|
|
7
7
|
__all__ = ("Event", "EventChannel", "EventListener")
|
|
@@ -36,11 +36,11 @@ class EventChannel:
|
|
|
36
36
|
|
|
37
37
|
yield
|
|
38
38
|
|
|
39
|
-
def add_listener(self, listener: EventListener):
|
|
39
|
+
def add_listener(self, listener: EventListener) -> Self:
|
|
40
40
|
self.__listeners.add(listener)
|
|
41
41
|
return self
|
|
42
42
|
|
|
43
|
-
def remove_listener(self, listener: EventListener):
|
|
43
|
+
def remove_listener(self, listener: EventListener) -> Self:
|
|
44
44
|
with suppress(KeyError):
|
|
45
45
|
self.__listeners.remove(listener)
|
|
46
46
|
|
injection/common/invertible.py
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Protocol,
|
|
4
|
+
from typing import Protocol, runtime_checkable
|
|
5
5
|
|
|
6
6
|
__all__ = ("Invertible", "SimpleInvertible")
|
|
7
7
|
|
|
8
|
-
_T_co = TypeVar("_T_co", covariant=True)
|
|
9
|
-
|
|
10
8
|
|
|
11
9
|
@runtime_checkable
|
|
12
|
-
class Invertible(Protocol
|
|
10
|
+
class Invertible[T](Protocol):
|
|
13
11
|
@abstractmethod
|
|
14
|
-
def __invert__(self) ->
|
|
12
|
+
def __invert__(self) -> T:
|
|
15
13
|
raise NotImplementedError
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
19
|
-
class SimpleInvertible(Invertible[
|
|
20
|
-
callable: Callable[[],
|
|
17
|
+
class SimpleInvertible[T](Invertible[T]):
|
|
18
|
+
callable: Callable[[], T]
|
|
21
19
|
|
|
22
|
-
def __invert__(self) ->
|
|
20
|
+
def __invert__(self) -> T:
|
|
23
21
|
return self.callable()
|
injection/common/lazy.py
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
from collections.abc import Callable, Iterator, Mapping
|
|
2
2
|
from types import MappingProxyType
|
|
3
|
-
from typing import TypeVar
|
|
4
3
|
|
|
5
4
|
from injection.common.invertible import Invertible
|
|
6
5
|
|
|
7
6
|
__all__ = ("Lazy", "LazyMapping")
|
|
8
7
|
|
|
9
|
-
_T = TypeVar("_T")
|
|
10
|
-
_K = TypeVar("_K")
|
|
11
|
-
_V = TypeVar("_V")
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
class Lazy(Invertible[_T]):
|
|
9
|
+
class Lazy[T](Invertible[T]):
|
|
15
10
|
__slots__ = ("__cache", "__is_set")
|
|
16
11
|
|
|
17
|
-
def __init__(self, factory: Callable[[],
|
|
12
|
+
def __init__(self, factory: Callable[[], T]):
|
|
18
13
|
self.__setup_cache(factory)
|
|
19
14
|
|
|
20
|
-
def __invert__(self) ->
|
|
15
|
+
def __invert__(self) -> T:
|
|
21
16
|
return next(self.__cache)
|
|
22
17
|
|
|
23
18
|
@property
|
|
24
19
|
def is_set(self) -> bool:
|
|
25
20
|
return self.__is_set
|
|
26
21
|
|
|
27
|
-
def __setup_cache(self, factory: Callable[[],
|
|
28
|
-
def cache_generator() -> Iterator[
|
|
22
|
+
def __setup_cache(self, factory: Callable[[], T]):
|
|
23
|
+
def cache_generator() -> Iterator[T]:
|
|
29
24
|
nonlocal factory
|
|
30
25
|
cached = factory()
|
|
31
26
|
self.__is_set = True
|
|
@@ -38,16 +33,16 @@ class Lazy(Invertible[_T]):
|
|
|
38
33
|
self.__is_set = False
|
|
39
34
|
|
|
40
35
|
|
|
41
|
-
class LazyMapping(Mapping[
|
|
36
|
+
class LazyMapping[K, V](Mapping[K, V]):
|
|
42
37
|
__slots__ = ("__lazy",)
|
|
43
38
|
|
|
44
|
-
def __init__(self, iterator: Iterator[tuple[
|
|
39
|
+
def __init__(self, iterator: Iterator[tuple[K, V]]):
|
|
45
40
|
self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
|
|
46
41
|
|
|
47
|
-
def __getitem__(self, key:
|
|
42
|
+
def __getitem__(self, key: K, /) -> V:
|
|
48
43
|
return (~self.__lazy)[key]
|
|
49
44
|
|
|
50
|
-
def __iter__(self) -> Iterator[
|
|
45
|
+
def __iter__(self) -> Iterator[K]:
|
|
51
46
|
yield from ~self.__lazy
|
|
52
47
|
|
|
53
48
|
def __len__(self) -> int:
|
injection/common/tools/type.py
CHANGED
|
@@ -1,48 +1,75 @@
|
|
|
1
|
-
from collections.abc import Iterable, Iterator
|
|
1
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
2
2
|
from inspect import get_annotations, isfunction
|
|
3
|
-
from types import
|
|
4
|
-
from typing import
|
|
3
|
+
from types import UnionType
|
|
4
|
+
from typing import (
|
|
5
|
+
Annotated,
|
|
6
|
+
Any,
|
|
7
|
+
NamedTuple,
|
|
8
|
+
Self,
|
|
9
|
+
Union,
|
|
10
|
+
get_args,
|
|
11
|
+
get_origin,
|
|
12
|
+
)
|
|
5
13
|
|
|
6
|
-
__all__ = ("
|
|
14
|
+
__all__ = ("TypeInfo", "TypeReport", "analyze_types", "get_return_types")
|
|
7
15
|
|
|
16
|
+
type TypeInfo[T] = type[T] | Callable[..., T] | Iterable[TypeInfo[T]] | UnionType
|
|
8
17
|
|
|
9
|
-
def format_type(cls: type | Any) -> str:
|
|
10
|
-
try:
|
|
11
|
-
return f"{cls.__module__}.{cls.__qualname__}"
|
|
12
|
-
except AttributeError:
|
|
13
|
-
return str(cls)
|
|
14
18
|
|
|
19
|
+
class TypeReport[T](NamedTuple):
|
|
20
|
+
origin: type[T]
|
|
21
|
+
args: tuple[Any, ...]
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
origin =
|
|
23
|
+
@property
|
|
24
|
+
def type(self) -> type[T]:
|
|
25
|
+
origin = self.origin
|
|
26
|
+
|
|
27
|
+
if args := self.args:
|
|
28
|
+
return origin[*args]
|
|
29
|
+
|
|
30
|
+
return origin
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def no_args(self) -> Self:
|
|
34
|
+
if self.args:
|
|
35
|
+
return type(self)(self.origin, ())
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def analyze_types(*types: type | Any) -> Iterator[TypeReport[Any]]:
|
|
41
|
+
for tp in types:
|
|
42
|
+
if tp is None:
|
|
21
43
|
continue
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
origin = get_origin(tp)
|
|
46
|
+
|
|
47
|
+
if origin is Union or isinstance(tp, UnionType):
|
|
48
|
+
inner_types = get_args(tp)
|
|
25
49
|
|
|
26
|
-
elif origin is Annotated
|
|
27
|
-
|
|
50
|
+
elif origin is Annotated:
|
|
51
|
+
inner_types = get_args(tp)[:1]
|
|
28
52
|
|
|
29
53
|
else:
|
|
30
|
-
yield origin
|
|
54
|
+
yield TypeReport(origin or tp, get_args(tp))
|
|
31
55
|
continue
|
|
32
56
|
|
|
33
|
-
yield from
|
|
57
|
+
yield from analyze_types(*inner_types)
|
|
34
58
|
|
|
35
59
|
|
|
36
|
-
def
|
|
37
|
-
for
|
|
38
|
-
if isinstance(
|
|
39
|
-
|
|
60
|
+
def get_return_types(*args: TypeInfo[Any]) -> Iterator[type | UnionType]:
|
|
61
|
+
for arg in args:
|
|
62
|
+
if isinstance(arg, Iterable) and not isinstance(
|
|
63
|
+
get_origin(arg) or arg,
|
|
64
|
+
type | str,
|
|
65
|
+
):
|
|
66
|
+
inner_args = arg
|
|
40
67
|
|
|
41
|
-
elif isfunction(
|
|
42
|
-
|
|
68
|
+
elif isfunction(arg):
|
|
69
|
+
inner_args = (get_annotations(arg, eval_str=True).get("return"),)
|
|
43
70
|
|
|
44
71
|
else:
|
|
45
|
-
yield
|
|
72
|
+
yield arg
|
|
46
73
|
continue
|
|
47
74
|
|
|
48
|
-
yield from
|
|
75
|
+
yield from get_return_types(*inner_args)
|
injection/core/module.py
CHANGED
|
@@ -14,7 +14,7 @@ from collections.abc import (
|
|
|
14
14
|
)
|
|
15
15
|
from contextlib import contextmanager, suppress
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
|
-
from enum import
|
|
17
|
+
from enum import StrEnum
|
|
18
18
|
from functools import partialmethod, singledispatchmethod, update_wrapper
|
|
19
19
|
from inspect import Signature, isclass
|
|
20
20
|
from queue import Queue
|
|
@@ -27,7 +27,7 @@ from typing import (
|
|
|
27
27
|
NamedTuple,
|
|
28
28
|
NoReturn,
|
|
29
29
|
Protocol,
|
|
30
|
-
|
|
30
|
+
Self,
|
|
31
31
|
runtime_checkable,
|
|
32
32
|
)
|
|
33
33
|
|
|
@@ -35,7 +35,12 @@ from injection.common.event import Event, EventChannel, EventListener
|
|
|
35
35
|
from injection.common.invertible import Invertible, SimpleInvertible
|
|
36
36
|
from injection.common.lazy import Lazy, LazyMapping
|
|
37
37
|
from injection.common.tools.threading import synchronized
|
|
38
|
-
from injection.common.tools.type import
|
|
38
|
+
from injection.common.tools.type import (
|
|
39
|
+
TypeInfo,
|
|
40
|
+
TypeReport,
|
|
41
|
+
analyze_types,
|
|
42
|
+
get_return_types,
|
|
43
|
+
)
|
|
39
44
|
from injection.exceptions import (
|
|
40
45
|
InjectionError,
|
|
41
46
|
ModuleError,
|
|
@@ -44,13 +49,18 @@ from injection.exceptions import (
|
|
|
44
49
|
NoInjectable,
|
|
45
50
|
)
|
|
46
51
|
|
|
47
|
-
__all__ = (
|
|
52
|
+
__all__ = (
|
|
53
|
+
"Injectable",
|
|
54
|
+
"InjectableFactory",
|
|
55
|
+
"Mode",
|
|
56
|
+
"ModeStr",
|
|
57
|
+
"Module",
|
|
58
|
+
"Priority",
|
|
59
|
+
"PriorityStr",
|
|
60
|
+
)
|
|
48
61
|
|
|
49
62
|
_logger = logging.getLogger(__name__)
|
|
50
63
|
|
|
51
|
-
_T = TypeVar("_T")
|
|
52
|
-
_T_co = TypeVar("_T_co", covariant=True)
|
|
53
|
-
|
|
54
64
|
"""
|
|
55
65
|
Events
|
|
56
66
|
"""
|
|
@@ -63,15 +73,15 @@ class ContainerEvent(Event, ABC):
|
|
|
63
73
|
|
|
64
74
|
@dataclass(frozen=True, slots=True)
|
|
65
75
|
class ContainerDependenciesUpdated(ContainerEvent):
|
|
66
|
-
|
|
76
|
+
reports: Collection[TypeReport]
|
|
67
77
|
mode: Mode
|
|
68
78
|
|
|
69
79
|
def __str__(self) -> str:
|
|
70
|
-
length = len(self.
|
|
71
|
-
|
|
80
|
+
length = len(self.reports)
|
|
81
|
+
formatted_types = ", ".join(f"`{report.type}`" for report in self.reports)
|
|
72
82
|
return (
|
|
73
|
-
f"{length} container dependenc{'ies' if length > 1 else 'y'} have
|
|
74
|
-
f"updated{f': {
|
|
83
|
+
f"{length} container dependenc{'ies' if length > 1 else 'y'} have "
|
|
84
|
+
f"been updated{f': {formatted_types}' if formatted_types else ''}."
|
|
75
85
|
)
|
|
76
86
|
|
|
77
87
|
|
|
@@ -134,12 +144,9 @@ Injectables
|
|
|
134
144
|
|
|
135
145
|
|
|
136
146
|
@runtime_checkable
|
|
137
|
-
class Injectable(Protocol
|
|
147
|
+
class Injectable[T](Protocol):
|
|
138
148
|
__slots__ = ()
|
|
139
149
|
|
|
140
|
-
def __init__(self, __factory: Callable[[], _T_co] = None, /):
|
|
141
|
-
pass
|
|
142
|
-
|
|
143
150
|
@property
|
|
144
151
|
def is_locked(self) -> bool:
|
|
145
152
|
return False
|
|
@@ -148,26 +155,26 @@ class Injectable(Protocol[_T_co]):
|
|
|
148
155
|
return
|
|
149
156
|
|
|
150
157
|
@abstractmethod
|
|
151
|
-
def get_instance(self) ->
|
|
158
|
+
def get_instance(self) -> T:
|
|
152
159
|
raise NotImplementedError
|
|
153
160
|
|
|
154
161
|
|
|
155
162
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
156
|
-
class BaseInjectable(Injectable[
|
|
157
|
-
factory: Callable[[],
|
|
163
|
+
class BaseInjectable[T](Injectable[T], ABC):
|
|
164
|
+
factory: Callable[[], T]
|
|
158
165
|
|
|
159
166
|
|
|
160
|
-
class NewInjectable(BaseInjectable[
|
|
167
|
+
class NewInjectable[T](BaseInjectable[T]):
|
|
161
168
|
__slots__ = ()
|
|
162
169
|
|
|
163
|
-
def get_instance(self) ->
|
|
170
|
+
def get_instance(self) -> T:
|
|
164
171
|
return self.factory()
|
|
165
172
|
|
|
166
173
|
|
|
167
|
-
class SingletonInjectable(BaseInjectable[
|
|
174
|
+
class SingletonInjectable[T](BaseInjectable[T]):
|
|
168
175
|
__slots__ = ("__dict__",)
|
|
169
176
|
|
|
170
|
-
|
|
177
|
+
__key: ClassVar[str] = "$instance"
|
|
171
178
|
|
|
172
179
|
@property
|
|
173
180
|
def cache(self) -> MutableMapping[str, Any]:
|
|
@@ -175,28 +182,28 @@ class SingletonInjectable(BaseInjectable[_T]):
|
|
|
175
182
|
|
|
176
183
|
@property
|
|
177
184
|
def is_locked(self) -> bool:
|
|
178
|
-
return self.
|
|
185
|
+
return self.__key in self.cache
|
|
179
186
|
|
|
180
187
|
def unlock(self):
|
|
181
188
|
self.cache.clear()
|
|
182
189
|
|
|
183
|
-
def get_instance(self) ->
|
|
190
|
+
def get_instance(self) -> T:
|
|
184
191
|
with suppress(KeyError):
|
|
185
|
-
return self.cache[self.
|
|
192
|
+
return self.cache[self.__key]
|
|
186
193
|
|
|
187
194
|
with synchronized():
|
|
188
195
|
instance = self.factory()
|
|
189
|
-
self.cache[self.
|
|
196
|
+
self.cache[self.__key] = instance
|
|
190
197
|
|
|
191
198
|
return instance
|
|
192
199
|
|
|
193
200
|
|
|
194
201
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
195
|
-
class ShouldBeInjectable(Injectable[
|
|
196
|
-
cls: type[
|
|
202
|
+
class ShouldBeInjectable[T](Injectable[T]):
|
|
203
|
+
cls: type[T]
|
|
197
204
|
|
|
198
205
|
def get_instance(self) -> NoReturn:
|
|
199
|
-
raise InjectionError(f"`{
|
|
206
|
+
raise InjectionError(f"`{self.cls}` should be an injectable.")
|
|
200
207
|
|
|
201
208
|
|
|
202
209
|
"""
|
|
@@ -209,7 +216,7 @@ class Broker(Protocol):
|
|
|
209
216
|
__slots__ = ()
|
|
210
217
|
|
|
211
218
|
@abstractmethod
|
|
212
|
-
def __getitem__(self, cls: type[
|
|
219
|
+
def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
|
|
213
220
|
raise NotImplementedError
|
|
214
221
|
|
|
215
222
|
@abstractmethod
|
|
@@ -222,7 +229,7 @@ class Broker(Protocol):
|
|
|
222
229
|
raise NotImplementedError
|
|
223
230
|
|
|
224
231
|
@abstractmethod
|
|
225
|
-
def unlock(self):
|
|
232
|
+
def unlock(self) -> Self:
|
|
226
233
|
raise NotImplementedError
|
|
227
234
|
|
|
228
235
|
|
|
@@ -231,7 +238,7 @@ Container
|
|
|
231
238
|
"""
|
|
232
239
|
|
|
233
240
|
|
|
234
|
-
class Mode(
|
|
241
|
+
class Mode(StrEnum):
|
|
235
242
|
FALLBACK = "fallback"
|
|
236
243
|
NORMAL = "normal"
|
|
237
244
|
OVERRIDE = "override"
|
|
@@ -241,36 +248,37 @@ class Mode(str, Enum):
|
|
|
241
248
|
return tuple(type(self)).index(self)
|
|
242
249
|
|
|
243
250
|
@classmethod
|
|
244
|
-
def get_default(cls):
|
|
251
|
+
def get_default(cls) -> Mode:
|
|
245
252
|
return cls.NORMAL
|
|
246
253
|
|
|
247
254
|
|
|
248
|
-
ModeStr = Literal["fallback", "normal", "override"]
|
|
255
|
+
type ModeStr = Literal["fallback", "normal", "override"]
|
|
249
256
|
|
|
250
257
|
|
|
251
|
-
class Record(NamedTuple):
|
|
252
|
-
injectable: Injectable
|
|
258
|
+
class Record[T](NamedTuple):
|
|
259
|
+
injectable: Injectable[T]
|
|
253
260
|
mode: Mode
|
|
254
261
|
|
|
255
262
|
|
|
256
263
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
257
264
|
class Container(Broker):
|
|
258
|
-
__records: dict[
|
|
265
|
+
__records: dict[TypeReport, Record] = field(default_factory=dict, init=False)
|
|
259
266
|
__channel: EventChannel = field(default_factory=EventChannel, init=False)
|
|
260
267
|
|
|
261
|
-
def __getitem__(self, cls: type[
|
|
262
|
-
for
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
|
|
269
|
+
for report in analyze_types(cls):
|
|
270
|
+
for scoped_report in dict.fromkeys((report, report.no_args)):
|
|
271
|
+
try:
|
|
272
|
+
injectable, _ = self.__records[scoped_report]
|
|
273
|
+
except KeyError:
|
|
274
|
+
continue
|
|
267
275
|
|
|
268
|
-
|
|
276
|
+
return injectable
|
|
269
277
|
|
|
270
278
|
raise NoInjectable(cls)
|
|
271
279
|
|
|
272
280
|
def __contains__(self, cls: type | UnionType, /) -> bool:
|
|
273
|
-
return any(
|
|
281
|
+
return any(report in self.__records for report in analyze_types(cls))
|
|
274
282
|
|
|
275
283
|
@property
|
|
276
284
|
def is_locked(self) -> bool:
|
|
@@ -281,16 +289,16 @@ class Container(Broker):
|
|
|
281
289
|
return frozenset(injectable for injectable, _ in self.__records.values())
|
|
282
290
|
|
|
283
291
|
@synchronized()
|
|
284
|
-
def update(
|
|
292
|
+
def update[T](
|
|
285
293
|
self,
|
|
286
|
-
classes: Iterable[type | UnionType],
|
|
287
|
-
injectable: Injectable,
|
|
294
|
+
classes: Iterable[type[T] | UnionType],
|
|
295
|
+
injectable: Injectable[T],
|
|
288
296
|
mode: Mode | ModeStr,
|
|
289
|
-
):
|
|
297
|
+
) -> Self:
|
|
290
298
|
mode = Mode(mode)
|
|
291
299
|
records = {
|
|
292
|
-
|
|
293
|
-
for
|
|
300
|
+
report: Record(injectable, mode)
|
|
301
|
+
for report in self.__prepare_reports_for_updating(classes, mode)
|
|
294
302
|
}
|
|
295
303
|
|
|
296
304
|
if records:
|
|
@@ -302,43 +310,43 @@ class Container(Broker):
|
|
|
302
310
|
return self
|
|
303
311
|
|
|
304
312
|
@synchronized()
|
|
305
|
-
def unlock(self):
|
|
313
|
+
def unlock(self) -> Self:
|
|
306
314
|
for injectable in self.__injectables:
|
|
307
315
|
injectable.unlock()
|
|
308
316
|
|
|
309
317
|
return self
|
|
310
318
|
|
|
311
|
-
def add_listener(self, listener: EventListener):
|
|
319
|
+
def add_listener(self, listener: EventListener) -> Self:
|
|
312
320
|
self.__channel.add_listener(listener)
|
|
313
321
|
return self
|
|
314
322
|
|
|
315
|
-
def notify(self, event: Event):
|
|
323
|
+
def notify(self, event: Event) -> ContextManager:
|
|
316
324
|
return self.__channel.dispatch(event)
|
|
317
325
|
|
|
318
|
-
def
|
|
326
|
+
def __prepare_reports_for_updating(
|
|
319
327
|
self,
|
|
320
328
|
classes: Iterable[type | UnionType],
|
|
321
329
|
mode: Mode,
|
|
322
|
-
) -> Iterator[
|
|
330
|
+
) -> Iterator[TypeReport]:
|
|
323
331
|
rank = mode.rank
|
|
324
332
|
|
|
325
|
-
for
|
|
333
|
+
for report in frozenset(analyze_types(*classes)):
|
|
326
334
|
try:
|
|
327
|
-
_, current_mode = self.__records[
|
|
335
|
+
_, current_mode = self.__records[report]
|
|
328
336
|
|
|
329
337
|
except KeyError:
|
|
330
338
|
pass
|
|
331
339
|
|
|
332
340
|
else:
|
|
333
|
-
if mode == current_mode:
|
|
341
|
+
if mode == current_mode and mode != Mode.OVERRIDE:
|
|
334
342
|
raise RuntimeError(
|
|
335
|
-
f"An injectable already exists for the class `{
|
|
343
|
+
f"An injectable already exists for the class `{report.type}`."
|
|
336
344
|
)
|
|
337
345
|
|
|
338
346
|
elif rank < current_mode.rank:
|
|
339
347
|
continue
|
|
340
348
|
|
|
341
|
-
yield
|
|
349
|
+
yield report
|
|
342
350
|
|
|
343
351
|
|
|
344
352
|
"""
|
|
@@ -346,47 +354,57 @@ Module
|
|
|
346
354
|
"""
|
|
347
355
|
|
|
348
356
|
|
|
349
|
-
class Priority(
|
|
357
|
+
class Priority(StrEnum):
|
|
350
358
|
LOW = "low"
|
|
351
359
|
HIGH = "high"
|
|
352
360
|
|
|
353
361
|
@classmethod
|
|
354
|
-
def get_default(cls):
|
|
362
|
+
def get_default(cls) -> Priority:
|
|
355
363
|
return cls.LOW
|
|
356
364
|
|
|
357
365
|
|
|
358
|
-
PriorityStr = Literal["low", "high"]
|
|
366
|
+
type PriorityStr = Literal["low", "high"]
|
|
359
367
|
|
|
368
|
+
type InjectableFactory[T] = Callable[[Callable[..., T]], Injectable[T]]
|
|
360
369
|
|
|
361
|
-
|
|
370
|
+
|
|
371
|
+
@dataclass(eq=False, frozen=True, slots=True)
|
|
362
372
|
class Module(EventListener, Broker):
|
|
363
373
|
name: str | None = field(default=None)
|
|
364
|
-
__channel: EventChannel = field(
|
|
365
|
-
|
|
374
|
+
__channel: EventChannel = field(
|
|
375
|
+
default_factory=EventChannel,
|
|
376
|
+
init=False,
|
|
377
|
+
repr=False,
|
|
378
|
+
)
|
|
379
|
+
__container: Container = field(
|
|
380
|
+
default_factory=Container,
|
|
381
|
+
init=False,
|
|
382
|
+
repr=False,
|
|
383
|
+
)
|
|
366
384
|
__modules: OrderedDict[Module, None] = field(
|
|
367
385
|
default_factory=OrderedDict,
|
|
368
386
|
init=False,
|
|
387
|
+
repr=False,
|
|
369
388
|
)
|
|
370
389
|
|
|
390
|
+
__instances: ClassVar[dict[str, Module]] = {}
|
|
391
|
+
|
|
371
392
|
def __post_init__(self):
|
|
372
393
|
self.__container.add_listener(self)
|
|
373
394
|
|
|
374
|
-
def __getitem__(self, cls: type[
|
|
395
|
+
def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
|
|
375
396
|
for broker in self.__brokers:
|
|
376
397
|
with suppress(KeyError):
|
|
377
398
|
return broker[cls]
|
|
378
399
|
|
|
379
400
|
raise NoInjectable(cls)
|
|
380
401
|
|
|
381
|
-
def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
|
|
402
|
+
def __setitem__[T](self, cls: type[T] | UnionType, injectable: Injectable[T], /):
|
|
382
403
|
self.update((cls,), injectable)
|
|
383
404
|
|
|
384
405
|
def __contains__(self, cls: type | UnionType, /) -> bool:
|
|
385
406
|
return any(cls in broker for broker in self.__brokers)
|
|
386
407
|
|
|
387
|
-
def __str__(self) -> str:
|
|
388
|
-
return self.name or object.__str__(self)
|
|
389
|
-
|
|
390
408
|
@property
|
|
391
409
|
def is_locked(self) -> bool:
|
|
392
410
|
return any(broker.is_locked for broker in self.__brokers)
|
|
@@ -396,20 +414,20 @@ class Module(EventListener, Broker):
|
|
|
396
414
|
yield from tuple(self.__modules)
|
|
397
415
|
yield self.__container
|
|
398
416
|
|
|
399
|
-
def injectable(
|
|
417
|
+
def injectable[T](
|
|
400
418
|
self,
|
|
401
|
-
wrapped: Callable[...,
|
|
419
|
+
wrapped: Callable[..., T] = None,
|
|
402
420
|
/,
|
|
403
421
|
*,
|
|
404
|
-
cls:
|
|
422
|
+
cls: InjectableFactory[T] = NewInjectable,
|
|
405
423
|
inject: bool = True,
|
|
406
|
-
on:
|
|
424
|
+
on: TypeInfo[T] = (),
|
|
407
425
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
408
426
|
):
|
|
409
427
|
def decorator(wp):
|
|
410
428
|
factory = self.inject(wp, return_factory=True) if inject else wp
|
|
411
429
|
injectable = cls(factory)
|
|
412
|
-
classes =
|
|
430
|
+
classes = get_return_types(wp, on)
|
|
413
431
|
self.update(classes, injectable, mode)
|
|
414
432
|
return wp
|
|
415
433
|
|
|
@@ -428,18 +446,18 @@ class Module(EventListener, Broker):
|
|
|
428
446
|
|
|
429
447
|
return decorator(wrapped) if wrapped else decorator
|
|
430
448
|
|
|
431
|
-
def set_constant(
|
|
449
|
+
def set_constant[T](
|
|
432
450
|
self,
|
|
433
|
-
instance:
|
|
434
|
-
on:
|
|
451
|
+
instance: T,
|
|
452
|
+
on: TypeInfo[T] = (),
|
|
435
453
|
*,
|
|
436
454
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
437
|
-
) ->
|
|
455
|
+
) -> T:
|
|
438
456
|
cls = type(instance)
|
|
439
457
|
self.injectable(
|
|
440
458
|
lambda: instance,
|
|
441
459
|
inject=False,
|
|
442
|
-
on=(cls, on),
|
|
460
|
+
on=(cls, on),
|
|
443
461
|
mode=mode,
|
|
444
462
|
)
|
|
445
463
|
return instance
|
|
@@ -458,7 +476,7 @@ class Module(EventListener, Broker):
|
|
|
458
476
|
|
|
459
477
|
function = InjectedFunction(wp)
|
|
460
478
|
|
|
461
|
-
@function.on_setup
|
|
479
|
+
@function.on_setup(block=False)
|
|
462
480
|
def listen():
|
|
463
481
|
function.update(self)
|
|
464
482
|
self.add_listener(function)
|
|
@@ -467,22 +485,22 @@ class Module(EventListener, Broker):
|
|
|
467
485
|
|
|
468
486
|
return decorator(wrapped) if wrapped else decorator
|
|
469
487
|
|
|
470
|
-
def resolve(self, cls: type[
|
|
488
|
+
def resolve[T](self, cls: type[T]) -> T:
|
|
471
489
|
injectable = self[cls]
|
|
472
490
|
return injectable.get_instance()
|
|
473
491
|
|
|
474
|
-
def get_instance(self, cls: type[
|
|
492
|
+
def get_instance[T](self, cls: type[T]) -> T | None:
|
|
475
493
|
try:
|
|
476
494
|
return self.resolve(cls)
|
|
477
495
|
except KeyError:
|
|
478
496
|
return None
|
|
479
497
|
|
|
480
|
-
def get_lazy_instance(
|
|
498
|
+
def get_lazy_instance[T](
|
|
481
499
|
self,
|
|
482
|
-
cls: type[
|
|
500
|
+
cls: type[T],
|
|
483
501
|
*,
|
|
484
502
|
cache: bool = False,
|
|
485
|
-
) -> Invertible[
|
|
503
|
+
) -> Invertible[T | None]:
|
|
486
504
|
if cache:
|
|
487
505
|
return Lazy(lambda: self.get_instance(cls))
|
|
488
506
|
|
|
@@ -490,21 +508,30 @@ class Module(EventListener, Broker):
|
|
|
490
508
|
function.set_owner(cls)
|
|
491
509
|
return SimpleInvertible(function)
|
|
492
510
|
|
|
493
|
-
def update(
|
|
511
|
+
def update[T](
|
|
494
512
|
self,
|
|
495
|
-
classes: Iterable[type | UnionType],
|
|
496
|
-
injectable: Injectable,
|
|
513
|
+
classes: Iterable[type[T] | UnionType],
|
|
514
|
+
injectable: Injectable[T],
|
|
497
515
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
498
|
-
):
|
|
516
|
+
) -> Self:
|
|
499
517
|
self.__container.update(classes, injectable, mode)
|
|
500
518
|
return self
|
|
501
519
|
|
|
520
|
+
def init_modules(self, *modules: Module) -> Self:
|
|
521
|
+
for module in tuple(self.__modules):
|
|
522
|
+
self.stop_using(module)
|
|
523
|
+
|
|
524
|
+
for module in modules:
|
|
525
|
+
self.use(module)
|
|
526
|
+
|
|
527
|
+
return self
|
|
528
|
+
|
|
502
529
|
def use(
|
|
503
530
|
self,
|
|
504
531
|
module: Module,
|
|
505
532
|
*,
|
|
506
533
|
priority: Priority | PriorityStr = Priority.get_default(),
|
|
507
|
-
):
|
|
534
|
+
) -> Self:
|
|
508
535
|
if module is self:
|
|
509
536
|
raise ModuleError("Module can't be used by itself.")
|
|
510
537
|
|
|
@@ -521,7 +548,7 @@ class Module(EventListener, Broker):
|
|
|
521
548
|
|
|
522
549
|
return self
|
|
523
550
|
|
|
524
|
-
def stop_using(self, module: Module):
|
|
551
|
+
def stop_using(self, module: Module) -> Self:
|
|
525
552
|
event = ModuleRemoved(self, module)
|
|
526
553
|
|
|
527
554
|
with suppress(KeyError):
|
|
@@ -542,7 +569,7 @@ class Module(EventListener, Broker):
|
|
|
542
569
|
yield
|
|
543
570
|
self.stop_using(module)
|
|
544
571
|
|
|
545
|
-
def change_priority(self, module: Module, priority: Priority | PriorityStr):
|
|
572
|
+
def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:
|
|
546
573
|
priority = Priority(priority)
|
|
547
574
|
event = ModulePriorityUpdated(self, module, priority)
|
|
548
575
|
|
|
@@ -552,17 +579,17 @@ class Module(EventListener, Broker):
|
|
|
552
579
|
return self
|
|
553
580
|
|
|
554
581
|
@synchronized()
|
|
555
|
-
def unlock(self):
|
|
582
|
+
def unlock(self) -> Self:
|
|
556
583
|
for broker in self.__brokers:
|
|
557
584
|
broker.unlock()
|
|
558
585
|
|
|
559
586
|
return self
|
|
560
587
|
|
|
561
|
-
def add_listener(self, listener: EventListener):
|
|
588
|
+
def add_listener(self, listener: EventListener) -> Self:
|
|
562
589
|
self.__channel.add_listener(listener)
|
|
563
590
|
return self
|
|
564
591
|
|
|
565
|
-
def remove_listener(self, listener: EventListener):
|
|
592
|
+
def remove_listener(self, listener: EventListener) -> Self:
|
|
566
593
|
self.__channel.remove_listener(listener)
|
|
567
594
|
return self
|
|
568
595
|
|
|
@@ -592,6 +619,19 @@ class Module(EventListener, Broker):
|
|
|
592
619
|
f"`{module}` can't be found in the modules used by `{self}`."
|
|
593
620
|
) from exc
|
|
594
621
|
|
|
622
|
+
@classmethod
|
|
623
|
+
def from_name(cls, name: str) -> Self:
|
|
624
|
+
with suppress(KeyError):
|
|
625
|
+
return cls.__instances[name]
|
|
626
|
+
|
|
627
|
+
instance = cls(name)
|
|
628
|
+
cls.__instances[name] = instance
|
|
629
|
+
return instance
|
|
630
|
+
|
|
631
|
+
@classmethod
|
|
632
|
+
def default(cls) -> Self:
|
|
633
|
+
return cls.from_name("default")
|
|
634
|
+
|
|
595
635
|
|
|
596
636
|
"""
|
|
597
637
|
InjectedFunction
|
|
@@ -621,15 +661,15 @@ class Dependencies:
|
|
|
621
661
|
return OrderedDict(self)
|
|
622
662
|
|
|
623
663
|
@classmethod
|
|
624
|
-
def from_mapping(cls, mapping: Mapping[str, Injectable]):
|
|
625
|
-
return cls(mapping
|
|
664
|
+
def from_mapping(cls, mapping: Mapping[str, Injectable]) -> Self:
|
|
665
|
+
return cls(mapping)
|
|
626
666
|
|
|
627
667
|
@classmethod
|
|
628
|
-
def empty(cls):
|
|
668
|
+
def empty(cls) -> Self:
|
|
629
669
|
return cls.from_mapping({})
|
|
630
670
|
|
|
631
671
|
@classmethod
|
|
632
|
-
def resolve(cls, signature: Signature, module: Module, owner: type = None):
|
|
672
|
+
def resolve(cls, signature: Signature, module: Module, owner: type = None) -> Self:
|
|
633
673
|
dependencies = LazyMapping(cls.__resolver(signature, module, owner))
|
|
634
674
|
return cls.from_mapping(dependencies)
|
|
635
675
|
|
|
@@ -705,7 +745,7 @@ class InjectedFunction(EventListener):
|
|
|
705
745
|
arguments = self.bind(args, kwargs)
|
|
706
746
|
return self.wrapped(*arguments.args, **arguments.kwargs)
|
|
707
747
|
|
|
708
|
-
def __get__(self, instance: object = None, owner: type = None):
|
|
748
|
+
def __get__(self, instance: object = None, owner: type = None) -> Self | MethodType:
|
|
709
749
|
if instance is None:
|
|
710
750
|
return self
|
|
711
751
|
|
|
@@ -739,7 +779,7 @@ class InjectedFunction(EventListener):
|
|
|
739
779
|
)
|
|
740
780
|
return Arguments(bound.args, bound.kwargs)
|
|
741
781
|
|
|
742
|
-
def set_owner(self, owner: type):
|
|
782
|
+
def set_owner(self, owner: type) -> Self:
|
|
743
783
|
if self.__dependencies.are_resolved:
|
|
744
784
|
raise TypeError(
|
|
745
785
|
"Function owner must be assigned before dependencies are resolved."
|
|
@@ -752,19 +792,19 @@ class InjectedFunction(EventListener):
|
|
|
752
792
|
return self
|
|
753
793
|
|
|
754
794
|
@synchronized()
|
|
755
|
-
def update(self, module: Module):
|
|
795
|
+
def update(self, module: Module) -> Self:
|
|
756
796
|
self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
|
|
757
797
|
return self
|
|
758
798
|
|
|
759
|
-
def on_setup(self, wrapped: Callable[[], Any] = None,
|
|
799
|
+
def on_setup(self, wrapped: Callable[[], Any] = None, /, *, block: bool = True):
|
|
760
800
|
def decorator(wp):
|
|
761
|
-
self.__setup_queue.put(wp)
|
|
801
|
+
self.__setup_queue.put(wp, block=block)
|
|
762
802
|
return wp
|
|
763
803
|
|
|
764
804
|
return decorator(wrapped) if wrapped else decorator
|
|
765
805
|
|
|
766
806
|
@singledispatchmethod
|
|
767
|
-
def on_event(self, event: Event, /) ->
|
|
807
|
+
def on_event(self, event: Event, /) -> None: # type: ignore
|
|
768
808
|
return None
|
|
769
809
|
|
|
770
810
|
@on_event.register
|
|
@@ -773,7 +813,7 @@ class InjectedFunction(EventListener):
|
|
|
773
813
|
yield
|
|
774
814
|
self.update(event.on_module)
|
|
775
815
|
|
|
776
|
-
def __set_signature(self):
|
|
816
|
+
def __set_signature(self) -> Self:
|
|
777
817
|
self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
|
|
778
818
|
return self
|
|
779
819
|
|
injection/exceptions.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from injection.common.tools.type import format_type
|
|
4
|
-
|
|
5
3
|
__all__ = (
|
|
6
4
|
"InjectionError",
|
|
7
5
|
"NoInjectable",
|
|
@@ -15,15 +13,15 @@ class InjectionError(Exception):
|
|
|
15
13
|
pass
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
class NoInjectable(KeyError, InjectionError):
|
|
16
|
+
class NoInjectable[T](KeyError, InjectionError):
|
|
19
17
|
__slots__ = ("__class",)
|
|
20
18
|
|
|
21
|
-
def __init__(self, cls: type | Any):
|
|
22
|
-
super().__init__(f"No injectable for `{
|
|
19
|
+
def __init__(self, cls: type[T] | Any):
|
|
20
|
+
super().__init__(f"No injectable for `{cls}`.")
|
|
23
21
|
self.__class = cls
|
|
24
22
|
|
|
25
23
|
@property
|
|
26
|
-
def cls(self) -> type:
|
|
24
|
+
def cls(self) -> type[T]:
|
|
27
25
|
return self.__class
|
|
28
26
|
|
|
29
27
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from rodi import ContainerProtocol
|
|
4
4
|
|
|
5
|
-
from injection import Module
|
|
5
|
+
from injection import Module
|
|
6
6
|
|
|
7
7
|
__all__ = ("InjectionServices",)
|
|
8
8
|
|
|
9
|
-
_T = TypeVar("_T")
|
|
10
|
-
|
|
11
9
|
|
|
12
10
|
class InjectionServices(ContainerProtocol):
|
|
13
11
|
"""
|
|
@@ -16,8 +14,8 @@ class InjectionServices(ContainerProtocol):
|
|
|
16
14
|
|
|
17
15
|
__slots__ = ("__module",)
|
|
18
16
|
|
|
19
|
-
def __init__(self, module: Module =
|
|
20
|
-
self.__module = module
|
|
17
|
+
def __init__(self, module: Module = None):
|
|
18
|
+
self.__module = module or Module.default()
|
|
21
19
|
|
|
22
20
|
def __contains__(self, item: Any) -> bool:
|
|
23
21
|
return item in self.__module
|
|
@@ -26,5 +24,5 @@ class InjectionServices(ContainerProtocol):
|
|
|
26
24
|
self.__module.injectable(obj_type)
|
|
27
25
|
return self
|
|
28
26
|
|
|
29
|
-
def resolve(self, obj_type: type[
|
|
27
|
+
def resolve[T](self, obj_type: type[T] | Any, *args, **kwargs) -> T:
|
|
30
28
|
return self.__module.resolve(obj_type)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
|
|
3
|
+
from injection import Module, ModulePriority
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
"set_test_constant",
|
|
7
|
+
"should_be_test_injectable",
|
|
8
|
+
"test_injectable",
|
|
9
|
+
"test_singleton",
|
|
10
|
+
"use_test_injectables",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_test_module() -> Module:
|
|
15
|
+
return Module.from_name("testing")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_module = get_test_module()
|
|
19
|
+
|
|
20
|
+
set_test_constant = _module.set_constant
|
|
21
|
+
should_be_test_injectable = _module.should_be_injectable
|
|
22
|
+
test_injectable = _module.injectable
|
|
23
|
+
test_singleton = _module.singleton
|
|
24
|
+
|
|
25
|
+
del _module
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def use_test_injectables(*, on: Module = None, test_module: Module = None):
|
|
30
|
+
on = on or Module.default()
|
|
31
|
+
test_module = test_module or get_test_module()
|
|
32
|
+
|
|
33
|
+
for module in (on, test_module):
|
|
34
|
+
module.unlock()
|
|
35
|
+
|
|
36
|
+
with on.use_temporarily(test_module, priority=ModulePriority.HIGH):
|
|
37
|
+
yield
|
|
38
|
+
on.unlock()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from contextlib import ContextDecorator
|
|
2
|
+
from typing import ContextManager
|
|
3
|
+
|
|
4
|
+
from injection import Module
|
|
5
|
+
|
|
6
|
+
_module: Module = ...
|
|
7
|
+
|
|
8
|
+
set_test_constant = _module.set_constant
|
|
9
|
+
should_be_test_injectable = _module.should_be_injectable
|
|
10
|
+
test_injectable = _module.injectable
|
|
11
|
+
test_singleton = _module.singleton
|
|
12
|
+
|
|
13
|
+
del _module
|
|
14
|
+
|
|
15
|
+
def use_test_injectables(
|
|
16
|
+
*,
|
|
17
|
+
on: Module = ...,
|
|
18
|
+
test_module: Module = ...,
|
|
19
|
+
) -> ContextManager | ContextDecorator:
|
|
20
|
+
"""
|
|
21
|
+
Context manager or decorator for temporary use test module.
|
|
22
|
+
"""
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-injection
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Fast and easy dependency injection framework.
|
|
5
5
|
Home-page: https://github.com/100nm/python-injection
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: dependencies,inject,injection
|
|
8
8
|
Author: remimd
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.12,<4
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
13
|
Project-URL: Repository, https://github.com/100nm/python-injection
|
|
16
14
|
Description-Content-Type: text/markdown
|
|
17
15
|
|
|
18
16
|
# Basic usage
|
|
19
17
|
|
|
20
|
-
##
|
|
18
|
+
## Register an injectable
|
|
21
19
|
|
|
22
20
|
> **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
|
|
23
21
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
injection/__init__.py,sha256=LhfwYFMBw6tJ7XA_C353w61OPt4IuQQgs3zIjrwMIz8,654
|
|
2
|
+
injection/__init__.pyi,sha256=dfIQAR8L8Yr7dM-DODR4czwUd2a7zzFQHzb8mr1Q6P0,6235
|
|
3
|
+
injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
injection/common/event.py,sha256=5Rdb2m3vAMCic8cQAVkStJDbrDrW_lk6kav8wYwmexM,1283
|
|
5
|
+
injection/common/invertible.py,sha256=noNcmJ96IQi0XJms0dyfrx_AvKZnQM0sZyxhc2l6qo0,527
|
|
6
|
+
injection/common/lazy.py,sha256=FEas6ewwOGWvRR8cflmyVvSLZd_-Fxd0QeIdvAM5I9c,1313
|
|
7
|
+
injection/common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
injection/common/tools/threading.py,sha256=HlvP6k_-eZaK8JbB2b9PP171IZVe_0W2oMYsw3ebdKA,187
|
|
9
|
+
injection/common/tools/type.py,sha256=ThM3Z1_gHmsdT4Jp7PlZgJY0lsufc4InPO65485Ugsg,1739
|
|
10
|
+
injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
|
|
11
|
+
injection/core/module.py,sha256=3FqU4XQgTNLI-VwpX1bKcRsLOaPeKSQ0sQuVsL_27I4,21625
|
|
12
|
+
injection/exceptions.py,sha256=RsWWiWwKSMU0vxXQqQSn6CKHLMrGu4SSzYUAy9OJRXk,626
|
|
13
|
+
injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
injection/integrations/blacksheep.py,sha256=82P_owhF3FKIJxxalnSic3AJnoOr1mkojkWMefpO6QI,731
|
|
15
|
+
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
injection/testing/__init__.py,sha256=xglKmIJgg2j1wt8qWJ_TtGfFyKmn7xE7b8kQhD2yEkQ,864
|
|
17
|
+
injection/testing/__init__.pyi,sha256=_u95cJVHBkt69HUvnu2bbfYWmi7GOH_1qyFyvC1PxBk,518
|
|
18
|
+
injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
|
|
19
|
+
python_injection-0.9.1.dist-info/METADATA,sha256=6vDRDhsWCG-FbwiHs-pvslfia8Fpe2n7hWJPoxJJbhg,3572
|
|
20
|
+
python_injection-0.9.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
21
|
+
python_injection-0.9.1.dist-info/RECORD,,
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
injection/__init__.py,sha256=Bf6S99E2srD3752xlJf3uAdiGIzY2YHOZafcwEiwY70,739
|
|
2
|
-
injection/__init__.pyi,sha256=fNTW5TUZQmaxPooaa_vJ_nyR_-DqZ13hSWHh_TsdeOo,5913
|
|
3
|
-
injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
injection/common/event.py,sha256=TvkFv-5zF_oUTPhh5U0BKD5HanvCJKHA0H7yceMRy5c,1261
|
|
5
|
-
injection/common/invertible.py,sha256=BZnkDg_NZDsTXGca5w5Wg_nYE3oRO_Wlodi2XtE55ck,595
|
|
6
|
-
injection/common/lazy.py,sha256=1C34uoG229Gl0DEUcD9-eQrL4K_oIofOLzdQ1SiY6rw,1401
|
|
7
|
-
injection/common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
injection/common/tools/threading.py,sha256=RAtzBFLVNJMflWIHxrP83fjafnFq8_JLgFoYQg8nVyE,182
|
|
9
|
-
injection/common/tools/type.py,sha256=05fD5UkUI1kPoFWEjQz4j266SYfJQMK-Ti88JXNxb_M,1276
|
|
10
|
-
injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
|
|
11
|
-
injection/core/module.py,sha256=9QAYw5u8ULev3UWZCJ3R2dH9-xCqjc5e_9_19hrVWWw,20626
|
|
12
|
-
injection/exceptions.py,sha256=f2lVSTAx-Nv89s0skn15y-sCkr656ROuWYs-XlrcEn8,683
|
|
13
|
-
injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
injection/integrations/blacksheep.py,sha256=dFXzrxkJRy9z44CcwG0ROYshT3zUZTVyuQkRy390RvU,765
|
|
15
|
-
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
|
|
17
|
-
python_injection-0.8.5.dist-info/METADATA,sha256=ZUrcGLYxHJC3IIwnBDJgsA3wlZ25zTNHeopIVWi4EHM,3672
|
|
18
|
-
python_injection-0.8.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
19
|
-
python_injection-0.8.5.dist-info/RECORD,,
|
|
File without changes
|