python-injection 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_injection-0.11.0 → python_injection-0.12.0}/PKG-INFO +9 -12
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/__init__.py +8 -9
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/__init__.pyi +48 -13
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/__init__.py +1 -1
- python_injection-0.12.0/injection/_core/common/asynchronous.py +54 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/invertible.py +2 -2
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/type.py +2 -5
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/descriptors.py +3 -3
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/hook.py +13 -10
- python_injection-0.12.0/injection/_core/injectables.py +106 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/module.py +260 -145
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/integrations/fastapi.py +17 -12
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/testing/__init__.pyi +1 -1
- {python_injection-0.11.0 → python_injection-0.12.0}/pyproject.toml +11 -10
- python_injection-0.11.0/injection/integrations/blacksheep.py +0 -34
- {python_injection-0.11.0 → python_injection-0.12.0}/README.md +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/event.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/_core/common/threading.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/exceptions.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/integrations/__init__.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/py.typed +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/testing/__init__.py +0 -0
- {python_injection-0.11.0 → python_injection-0.12.0}/injection/utils.py +0 -0
@@ -1,26 +1,23 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: python-injection
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.0
|
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,dependency,inject,injection
|
8
8
|
Author: remimd
|
9
|
-
Requires-Python: >=3.12
|
9
|
+
Requires-Python: >=3.12, <4
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
11
|
-
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
13
|
-
Classifier: Natural Language :: English
|
14
|
-
Classifier: Operating System :: OS Independent
|
15
|
-
Classifier: Programming Language :: Python
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
19
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
20
11
|
Classifier: Topic :: Software Development :: Libraries
|
21
12
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
22
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
23
14
|
Classifier: Topic :: Software Development :: Testing
|
15
|
+
Classifier: Programming Language :: Python
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
18
|
+
Classifier: Operating System :: OS Independent
|
19
|
+
Classifier: Intended Audience :: Developers
|
20
|
+
Classifier: Natural Language :: English
|
24
21
|
Classifier: Typing :: Typed
|
25
22
|
Project-URL: Repository, https://github.com/100nm/python-injection
|
26
23
|
Description-Content-Type: text/markdown
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from ._core.descriptors import LazyInstance
|
2
|
-
from ._core.
|
2
|
+
from ._core.injectables import Injectable
|
3
|
+
from ._core.module import Mode, Module, Priority, mod
|
3
4
|
|
4
5
|
__all__ = (
|
5
6
|
"Injectable",
|
@@ -7,6 +8,9 @@ __all__ = (
|
|
7
8
|
"Mode",
|
8
9
|
"Module",
|
9
10
|
"Priority",
|
11
|
+
"afind_instance",
|
12
|
+
"aget_instance",
|
13
|
+
"aget_lazy_instance",
|
10
14
|
"constant",
|
11
15
|
"find_instance",
|
12
16
|
"get_instance",
|
@@ -19,14 +23,9 @@ __all__ = (
|
|
19
23
|
"singleton",
|
20
24
|
)
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return Module.default()
|
26
|
-
|
27
|
-
return Module.from_name(name)
|
28
|
-
|
29
|
-
|
26
|
+
afind_instance = mod().afind_instance
|
27
|
+
aget_instance = mod().aget_instance
|
28
|
+
aget_lazy_instance = mod().aget_lazy_instance
|
30
29
|
constant = mod().constant
|
31
30
|
find_instance = mod().find_instance
|
32
31
|
get_instance = mod().get_instance
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from collections.abc import Callable
|
2
|
+
from collections.abc import Awaitable, Callable
|
3
3
|
from contextlib import ContextDecorator
|
4
4
|
from enum import Enum
|
5
5
|
from logging import Logger
|
@@ -21,6 +21,9 @@ from ._core.module import ModeStr, PriorityStr
|
|
21
21
|
|
22
22
|
_: Module = ...
|
23
23
|
|
24
|
+
afind_instance = _.afind_instance
|
25
|
+
aget_instance = _.aget_instance
|
26
|
+
aget_lazy_instance = _.aget_lazy_instance
|
24
27
|
constant = _.constant
|
25
28
|
find_instance = _.find_instance
|
26
29
|
get_instance = _.get_instance
|
@@ -53,59 +56,59 @@ class Module:
|
|
53
56
|
def __contains__(self, cls: _InputType[Any], /) -> bool: ...
|
54
57
|
@property
|
55
58
|
def is_locked(self) -> bool: ...
|
56
|
-
def inject[**P, T](self, wrapped: Callable[P, T] = ..., /)
|
59
|
+
def inject[**P, T](self, wrapped: Callable[P, T] = ..., /) -> Any:
|
57
60
|
"""
|
58
61
|
Decorator applicable to a class or function. Inject function dependencies using
|
59
62
|
parameter type annotations. If applied to a class, the dependencies resolved
|
60
63
|
will be those of the `__init__` method.
|
61
64
|
"""
|
62
65
|
|
63
|
-
def injectable[**P, T](
|
66
|
+
def injectable[**P, T](
|
64
67
|
self,
|
65
|
-
wrapped: Callable[P, T] = ...,
|
68
|
+
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
|
66
69
|
/,
|
67
70
|
*,
|
68
71
|
cls: _InjectableFactory[T] = ...,
|
69
72
|
inject: bool = ...,
|
70
73
|
on: _TypeInfo[T] = ...,
|
71
74
|
mode: Mode | ModeStr = ...,
|
72
|
-
):
|
75
|
+
) -> Any:
|
73
76
|
"""
|
74
77
|
Decorator applicable to a class or function. It is used to indicate how the
|
75
78
|
injectable will be constructed. At injection time, a new instance will be
|
76
79
|
injected each time.
|
77
80
|
"""
|
78
81
|
|
79
|
-
def singleton[**P, T](
|
82
|
+
def singleton[**P, T](
|
80
83
|
self,
|
81
|
-
wrapped: Callable[P, T] = ...,
|
84
|
+
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
|
82
85
|
/,
|
83
86
|
*,
|
84
87
|
inject: bool = ...,
|
85
88
|
on: _TypeInfo[T] = ...,
|
86
89
|
mode: Mode | ModeStr = ...,
|
87
|
-
):
|
90
|
+
) -> Any:
|
88
91
|
"""
|
89
92
|
Decorator applicable to a class or function. It is used to indicate how the
|
90
93
|
singleton will be constructed. At injection time, the injected instance will
|
91
94
|
always be the same.
|
92
95
|
"""
|
93
96
|
|
94
|
-
def should_be_injectable[T](self, wrapped: type[T] = ..., /)
|
97
|
+
def should_be_injectable[T](self, wrapped: type[T] = ..., /) -> Any:
|
95
98
|
"""
|
96
99
|
Decorator applicable to a class. It is used to specify whether an injectable
|
97
100
|
should be registered. Raise an exception at injection time if the class isn't
|
98
101
|
registered.
|
99
102
|
"""
|
100
103
|
|
101
|
-
def constant[T](
|
104
|
+
def constant[T](
|
102
105
|
self,
|
103
106
|
wrapped: type[T] = ...,
|
104
107
|
/,
|
105
108
|
*,
|
106
109
|
on: _TypeInfo[T] = ...,
|
107
110
|
mode: Mode | ModeStr = ...,
|
108
|
-
):
|
111
|
+
) -> Any:
|
109
112
|
"""
|
110
113
|
Decorator applicable to a class or function. It is used to indicate how the
|
111
114
|
constant is constructed. At injection time, the injected instance will always
|
@@ -131,12 +134,25 @@ class Module:
|
|
131
134
|
wrapped: Callable[P, T],
|
132
135
|
/,
|
133
136
|
) -> Callable[P, T]: ...
|
137
|
+
async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
|
134
138
|
def find_instance[T](self, cls: _InputType[T]) -> T:
|
135
139
|
"""
|
136
140
|
Function used to retrieve an instance associated with the type passed in
|
137
141
|
parameter or an exception will be raised.
|
138
142
|
"""
|
139
143
|
|
144
|
+
@overload
|
145
|
+
async def aget_instance[T, Default](
|
146
|
+
self,
|
147
|
+
cls: _InputType[T],
|
148
|
+
default: Default,
|
149
|
+
) -> T | Default: ...
|
150
|
+
@overload
|
151
|
+
async def aget_instance[T](
|
152
|
+
self,
|
153
|
+
cls: _InputType[T],
|
154
|
+
default: None = ...,
|
155
|
+
) -> T | None: ...
|
140
156
|
@overload
|
141
157
|
def get_instance[T, Default](
|
142
158
|
self,
|
@@ -149,12 +165,28 @@ class Module:
|
|
149
165
|
"""
|
150
166
|
|
151
167
|
@overload
|
152
|
-
def get_instance[T
|
168
|
+
def get_instance[T](
|
153
169
|
self,
|
154
170
|
cls: _InputType[T],
|
155
171
|
default: None = ...,
|
156
172
|
) -> T | None: ...
|
157
173
|
@overload
|
174
|
+
def aget_lazy_instance[T, Default](
|
175
|
+
self,
|
176
|
+
cls: _InputType[T],
|
177
|
+
default: Default,
|
178
|
+
*,
|
179
|
+
cache: bool = ...,
|
180
|
+
) -> Awaitable[T | Default]: ...
|
181
|
+
@overload
|
182
|
+
def aget_lazy_instance[T](
|
183
|
+
self,
|
184
|
+
cls: _InputType[T],
|
185
|
+
default: None = ...,
|
186
|
+
*,
|
187
|
+
cache: bool = ...,
|
188
|
+
) -> Awaitable[T | None]: ...
|
189
|
+
@overload
|
158
190
|
def get_lazy_instance[T, Default](
|
159
191
|
self,
|
160
192
|
cls: _InputType[T],
|
@@ -172,7 +204,7 @@ class Module:
|
|
172
204
|
"""
|
173
205
|
|
174
206
|
@overload
|
175
|
-
def get_lazy_instance[T
|
207
|
+
def get_lazy_instance[T](
|
176
208
|
self,
|
177
209
|
cls: _InputType[T],
|
178
210
|
default: None = ...,
|
@@ -229,6 +261,7 @@ class Module:
|
|
229
261
|
Function to unlock the module by deleting cached instances of singletons.
|
230
262
|
"""
|
231
263
|
|
264
|
+
async def all_ready(self) -> None: ...
|
232
265
|
def add_logger(self, logger: Logger) -> Self: ...
|
233
266
|
@classmethod
|
234
267
|
def from_name(cls, name: str) -> Module:
|
@@ -253,6 +286,8 @@ class Injectable[T](Protocol):
|
|
253
286
|
def is_locked(self) -> bool: ...
|
254
287
|
def unlock(self) -> None: ...
|
255
288
|
@abstractmethod
|
289
|
+
async def aget_instance(self) -> T: ...
|
290
|
+
@abstractmethod
|
256
291
|
def get_instance(self) -> T: ...
|
257
292
|
|
258
293
|
@final
|
@@ -49,5 +49,5 @@ def standardize_input_classes[T](
|
|
49
49
|
@Locator.static_hooks.on_update
|
50
50
|
def standardize_classes[T](*_: Any, **__: Any) -> HookGenerator[Updater[T]]:
|
51
51
|
updater = yield
|
52
|
-
updater.classes =
|
52
|
+
updater.classes = frozenset(standardize_types(*updater.classes))
|
53
53
|
return updater
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
from collections.abc import Awaitable, Callable, Generator
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any, NoReturn, Protocol, override, runtime_checkable
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
8
|
+
class SimpleAwaitable[T](Awaitable[T]):
|
9
|
+
callable: Callable[..., Awaitable[T]]
|
10
|
+
|
11
|
+
@override
|
12
|
+
def __await__(self) -> Generator[Any, Any, T]:
|
13
|
+
return self.callable().__await__()
|
14
|
+
|
15
|
+
|
16
|
+
@runtime_checkable
|
17
|
+
class Caller[**P, T](Protocol):
|
18
|
+
__slots__ = ()
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
26
|
+
raise NotImplementedError
|
27
|
+
|
28
|
+
|
29
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
30
|
+
class AsyncCaller[**P, T](Caller[P, T]):
|
31
|
+
callable: Callable[P, Awaitable[T]]
|
32
|
+
|
33
|
+
@override
|
34
|
+
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
35
|
+
return await self.callable(*args, **kwargs)
|
36
|
+
|
37
|
+
@override
|
38
|
+
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
|
39
|
+
raise RuntimeError(
|
40
|
+
"Synchronous call isn't supported for an asynchronous Callable."
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
45
|
+
class SyncCaller[**P, T](Caller[P, T]):
|
46
|
+
callable: Callable[P, T]
|
47
|
+
|
48
|
+
@override
|
49
|
+
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
50
|
+
return self.callable(*args, **kwargs)
|
51
|
+
|
52
|
+
@override
|
53
|
+
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
54
|
+
return self.callable(*args, **kwargs)
|
@@ -13,8 +13,8 @@ class Invertible[T](Protocol):
|
|
13
13
|
|
14
14
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
15
15
|
class SimpleInvertible[T](Invertible[T]):
|
16
|
-
|
16
|
+
callable: Callable[..., T]
|
17
17
|
|
18
18
|
@override
|
19
19
|
def __invert__(self) -> T:
|
20
|
-
return self.
|
20
|
+
return self.callable()
|
@@ -1,5 +1,5 @@
|
|
1
|
-
from collections.abc import
|
2
|
-
from inspect import
|
1
|
+
from collections.abc import Callable, Iterable, Iterator
|
2
|
+
from inspect import isfunction
|
3
3
|
from types import GenericAlias, UnionType
|
4
4
|
from typing import (
|
5
5
|
Annotated,
|
@@ -24,9 +24,6 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
|
|
24
24
|
inner_args = arg
|
25
25
|
|
26
26
|
elif isfunction(arg) and (return_type := get_type_hints(arg).get("return")):
|
27
|
-
if iscoroutinefunction(arg):
|
28
|
-
return_type = Awaitable[return_type] # type: ignore[valid-type]
|
29
|
-
|
30
27
|
inner_args = (return_type,)
|
31
28
|
|
32
29
|
else:
|
@@ -2,7 +2,7 @@ from typing import Self
|
|
2
2
|
|
3
3
|
from injection._core.common.invertible import Invertible
|
4
4
|
from injection._core.common.type import InputType
|
5
|
-
from injection._core.module import Module
|
5
|
+
from injection._core.module import Module, mod
|
6
6
|
|
7
7
|
|
8
8
|
class LazyInstance[T]:
|
@@ -11,8 +11,8 @@ class LazyInstance[T]:
|
|
11
11
|
__value: Invertible[T]
|
12
12
|
|
13
13
|
def __init__(self, cls: InputType[T], module: Module | None = None) -> None:
|
14
|
-
module = module or
|
15
|
-
self.__value = module.get_lazy_instance(cls, default=NotImplemented)
|
14
|
+
module = module or mod()
|
15
|
+
self.__value = module.get_lazy_instance(cls, default=NotImplemented)
|
16
16
|
|
17
17
|
def __get__(
|
18
18
|
self,
|
@@ -2,12 +2,13 @@ import itertools
|
|
2
2
|
from collections.abc import Callable, Generator, Iterator
|
3
3
|
from dataclasses import dataclass, field
|
4
4
|
from inspect import isclass, isgeneratorfunction
|
5
|
-
from typing import Any, Self
|
5
|
+
from typing import Any, Self, TypeGuard
|
6
6
|
|
7
7
|
from injection.exceptions import HookError
|
8
8
|
|
9
9
|
type HookGenerator[T] = Generator[None, T, T]
|
10
|
-
type
|
10
|
+
type HookGeneratorFunction[**P, T] = Callable[P, HookGenerator[T]]
|
11
|
+
type HookFunction[**P, T] = Callable[P, T] | HookGeneratorFunction[P, T]
|
11
12
|
|
12
13
|
|
13
14
|
@dataclass(eq=False, frozen=True, slots=True)
|
@@ -18,12 +19,12 @@ class Hook[**P, T]:
|
|
18
19
|
repr=False,
|
19
20
|
)
|
20
21
|
|
21
|
-
def __call__(
|
22
|
+
def __call__(
|
22
23
|
self,
|
23
24
|
wrapped: HookFunction[P, T] | type[HookFunction[P, T]] | None = None,
|
24
25
|
/,
|
25
|
-
):
|
26
|
-
def decorator(wp
|
26
|
+
) -> Any:
|
27
|
+
def decorator(wp: Any) -> Any:
|
27
28
|
self.add(wp() if isclass(wp) else wp)
|
28
29
|
return wp
|
29
30
|
|
@@ -48,11 +49,11 @@ class Hook[**P, T]:
|
|
48
49
|
handler: Callable[P, T],
|
49
50
|
function: HookFunction[P, T],
|
50
51
|
) -> Callable[P, T]:
|
51
|
-
if not cls.
|
52
|
+
if not cls.__is_hook_generator_function(function):
|
52
53
|
return function # type: ignore[return-value]
|
53
54
|
|
54
55
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
55
|
-
hook: HookGenerator[T] = function(*args, **kwargs)
|
56
|
+
hook: HookGenerator[T] = function(*args, **kwargs)
|
56
57
|
|
57
58
|
try:
|
58
59
|
next(hook)
|
@@ -88,9 +89,11 @@ class Hook[**P, T]:
|
|
88
89
|
return handler
|
89
90
|
|
90
91
|
@staticmethod
|
91
|
-
def
|
92
|
-
|
93
|
-
|
92
|
+
def __is_hook_generator_function[**_P, _T](
|
93
|
+
function: HookFunction[_P, _T],
|
94
|
+
) -> TypeGuard[HookGeneratorFunction[_P, _T]]:
|
95
|
+
for fn in function, getattr(function, "__call__", None):
|
96
|
+
if isgeneratorfunction(fn):
|
94
97
|
return True
|
95
98
|
|
96
99
|
return False
|
@@ -0,0 +1,106 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from collections.abc import MutableMapping
|
3
|
+
from contextlib import suppress
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Any, ClassVar, NoReturn, Protocol, override, runtime_checkable
|
6
|
+
|
7
|
+
from injection._core.common.asynchronous import Caller
|
8
|
+
from injection._core.common.threading import synchronized
|
9
|
+
from injection.exceptions import InjectionError
|
10
|
+
|
11
|
+
|
12
|
+
@runtime_checkable
|
13
|
+
class Injectable[T](Protocol):
|
14
|
+
__slots__ = ()
|
15
|
+
|
16
|
+
@property
|
17
|
+
def is_locked(self) -> bool:
|
18
|
+
return False
|
19
|
+
|
20
|
+
def unlock(self) -> None:
|
21
|
+
return
|
22
|
+
|
23
|
+
@abstractmethod
|
24
|
+
async def aget_instance(self) -> T:
|
25
|
+
raise NotImplementedError
|
26
|
+
|
27
|
+
@abstractmethod
|
28
|
+
def get_instance(self) -> T:
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
|
32
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
33
|
+
class BaseInjectable[T](Injectable[T], ABC):
|
34
|
+
factory: Caller[..., T]
|
35
|
+
|
36
|
+
|
37
|
+
class SimpleInjectable[T](BaseInjectable[T]):
|
38
|
+
__slots__ = ()
|
39
|
+
|
40
|
+
@override
|
41
|
+
async def aget_instance(self) -> T:
|
42
|
+
return await self.factory.acall()
|
43
|
+
|
44
|
+
@override
|
45
|
+
def get_instance(self) -> T:
|
46
|
+
return self.factory.call()
|
47
|
+
|
48
|
+
|
49
|
+
class SingletonInjectable[T](BaseInjectable[T]):
|
50
|
+
__slots__ = ("__dict__",)
|
51
|
+
|
52
|
+
__key: ClassVar[str] = "$instance"
|
53
|
+
|
54
|
+
@property
|
55
|
+
def cache(self) -> MutableMapping[str, Any]:
|
56
|
+
return self.__dict__
|
57
|
+
|
58
|
+
@property
|
59
|
+
@override
|
60
|
+
def is_locked(self) -> bool:
|
61
|
+
return self.__key in self.cache
|
62
|
+
|
63
|
+
@override
|
64
|
+
def unlock(self) -> None:
|
65
|
+
self.cache.clear()
|
66
|
+
|
67
|
+
@override
|
68
|
+
async def aget_instance(self) -> T:
|
69
|
+
with suppress(KeyError):
|
70
|
+
return self.__check_instance()
|
71
|
+
|
72
|
+
with synchronized():
|
73
|
+
instance = await self.factory.acall()
|
74
|
+
self.__set_instance(instance)
|
75
|
+
|
76
|
+
return instance
|
77
|
+
|
78
|
+
@override
|
79
|
+
def get_instance(self) -> T:
|
80
|
+
with suppress(KeyError):
|
81
|
+
return self.__check_instance()
|
82
|
+
|
83
|
+
with synchronized():
|
84
|
+
instance = self.factory.call()
|
85
|
+
self.__set_instance(instance)
|
86
|
+
|
87
|
+
return instance
|
88
|
+
|
89
|
+
def __check_instance(self) -> T:
|
90
|
+
return self.cache[self.__key]
|
91
|
+
|
92
|
+
def __set_instance(self, value: T) -> None:
|
93
|
+
self.cache[self.__key] = value
|
94
|
+
|
95
|
+
|
96
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
97
|
+
class ShouldBeInjectable[T](Injectable[T]):
|
98
|
+
cls: type[T]
|
99
|
+
|
100
|
+
@override
|
101
|
+
async def aget_instance(self) -> T:
|
102
|
+
return self.get_instance()
|
103
|
+
|
104
|
+
@override
|
105
|
+
def get_instance(self) -> NoReturn:
|
106
|
+
raise InjectionError(f"`{self.cls}` should be an injectable.")
|
@@ -1,21 +1,23 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import inspect
|
4
5
|
from abc import ABC, abstractmethod
|
5
6
|
from collections import OrderedDict
|
6
7
|
from collections.abc import (
|
8
|
+
AsyncIterator,
|
9
|
+
Awaitable,
|
7
10
|
Callable,
|
8
11
|
Collection,
|
9
12
|
Iterable,
|
10
13
|
Iterator,
|
11
14
|
Mapping,
|
12
|
-
MutableMapping,
|
13
15
|
)
|
14
16
|
from contextlib import contextmanager, suppress
|
15
17
|
from dataclasses import dataclass, field
|
16
18
|
from enum import StrEnum
|
17
19
|
from functools import partialmethod, singledispatchmethod, update_wrapper
|
18
|
-
from inspect import Signature, isclass
|
20
|
+
from inspect import Signature, isclass, iscoroutinefunction
|
19
21
|
from logging import Logger, getLogger
|
20
22
|
from queue import Empty, Queue
|
21
23
|
from types import MethodType
|
@@ -25,22 +27,34 @@ from typing import (
|
|
25
27
|
ContextManager,
|
26
28
|
Literal,
|
27
29
|
NamedTuple,
|
28
|
-
NoReturn,
|
29
30
|
Protocol,
|
30
31
|
Self,
|
32
|
+
TypeGuard,
|
33
|
+
overload,
|
31
34
|
override,
|
32
35
|
runtime_checkable,
|
33
36
|
)
|
34
37
|
from uuid import uuid4
|
35
38
|
|
39
|
+
from injection._core.common.asynchronous import (
|
40
|
+
AsyncCaller,
|
41
|
+
Caller,
|
42
|
+
SimpleAwaitable,
|
43
|
+
SyncCaller,
|
44
|
+
)
|
36
45
|
from injection._core.common.event import Event, EventChannel, EventListener
|
37
46
|
from injection._core.common.invertible import Invertible, SimpleInvertible
|
38
47
|
from injection._core.common.lazy import Lazy, LazyMapping
|
39
48
|
from injection._core.common.threading import synchronized
|
40
49
|
from injection._core.common.type import InputType, TypeInfo, get_return_types
|
41
50
|
from injection._core.hook import Hook, apply_hooks
|
51
|
+
from injection._core.injectables import (
|
52
|
+
Injectable,
|
53
|
+
ShouldBeInjectable,
|
54
|
+
SimpleInjectable,
|
55
|
+
SingletonInjectable,
|
56
|
+
)
|
42
57
|
from injection.exceptions import (
|
43
|
-
InjectionError,
|
44
58
|
ModuleError,
|
45
59
|
ModuleLockError,
|
46
60
|
ModuleNotUsedError,
|
@@ -129,86 +143,6 @@ class ModulePriorityUpdated(ModuleEvent):
|
|
129
143
|
)
|
130
144
|
|
131
145
|
|
132
|
-
"""
|
133
|
-
Injectables
|
134
|
-
"""
|
135
|
-
|
136
|
-
|
137
|
-
@runtime_checkable
|
138
|
-
class Injectable[T](Protocol):
|
139
|
-
__slots__ = ()
|
140
|
-
|
141
|
-
@property
|
142
|
-
def is_locked(self) -> bool:
|
143
|
-
return False
|
144
|
-
|
145
|
-
def unlock(self) -> None:
|
146
|
-
return
|
147
|
-
|
148
|
-
@abstractmethod
|
149
|
-
def get_instance(self) -> T:
|
150
|
-
raise NotImplementedError
|
151
|
-
|
152
|
-
|
153
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
154
|
-
class BaseInjectable[T](Injectable[T], ABC):
|
155
|
-
factory: Callable[..., T]
|
156
|
-
|
157
|
-
|
158
|
-
class SimpleInjectable[T](BaseInjectable[T]):
|
159
|
-
__slots__ = ()
|
160
|
-
|
161
|
-
@override
|
162
|
-
def get_instance(self) -> T:
|
163
|
-
return self.factory()
|
164
|
-
|
165
|
-
|
166
|
-
class SingletonInjectable[T](BaseInjectable[T]):
|
167
|
-
__slots__ = ("__dict__",)
|
168
|
-
|
169
|
-
__key: ClassVar[str] = "$instance"
|
170
|
-
|
171
|
-
@property
|
172
|
-
def cache(self) -> MutableMapping[str, Any]:
|
173
|
-
return self.__dict__
|
174
|
-
|
175
|
-
@property
|
176
|
-
@override
|
177
|
-
def is_locked(self) -> bool:
|
178
|
-
return self.__key in self.cache
|
179
|
-
|
180
|
-
@override
|
181
|
-
def unlock(self) -> None:
|
182
|
-
self.cache.clear()
|
183
|
-
|
184
|
-
@override
|
185
|
-
def get_instance(self) -> T:
|
186
|
-
with suppress(KeyError):
|
187
|
-
return self.cache[self.__key]
|
188
|
-
|
189
|
-
with synchronized():
|
190
|
-
instance = self.factory()
|
191
|
-
self.cache[self.__key] = instance
|
192
|
-
|
193
|
-
return instance
|
194
|
-
|
195
|
-
|
196
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
197
|
-
class ShouldBeInjectable[T](Injectable[T]):
|
198
|
-
cls: type[T]
|
199
|
-
|
200
|
-
@override
|
201
|
-
def get_instance(self) -> NoReturn:
|
202
|
-
raise InjectionError(f"`{self.cls}` should be an injectable.")
|
203
|
-
|
204
|
-
@classmethod
|
205
|
-
def from_callable(cls, callable: Callable[..., T]) -> Self:
|
206
|
-
if not isclass(callable):
|
207
|
-
raise TypeError(f"`{callable}` should be a class.")
|
208
|
-
|
209
|
-
return cls(callable)
|
210
|
-
|
211
|
-
|
212
146
|
"""
|
213
147
|
Broker
|
214
148
|
"""
|
@@ -235,6 +169,10 @@ class Broker(Protocol):
|
|
235
169
|
def unlock(self) -> Self:
|
236
170
|
raise NotImplementedError
|
237
171
|
|
172
|
+
@abstractmethod
|
173
|
+
async def all_ready(self) -> None:
|
174
|
+
raise NotImplementedError
|
175
|
+
|
238
176
|
|
239
177
|
"""
|
240
178
|
Locator
|
@@ -257,7 +195,7 @@ class Mode(StrEnum):
|
|
257
195
|
|
258
196
|
type ModeStr = Literal["fallback", "normal", "override"]
|
259
197
|
|
260
|
-
type InjectableFactory[T] = Callable[[
|
198
|
+
type InjectableFactory[T] = Callable[[Caller[..., T]], Injectable[T]]
|
261
199
|
|
262
200
|
|
263
201
|
class Record[T](NamedTuple):
|
@@ -267,7 +205,7 @@ class Record[T](NamedTuple):
|
|
267
205
|
|
268
206
|
@dataclass(repr=False, eq=False, kw_only=True, slots=True)
|
269
207
|
class Updater[T]:
|
270
|
-
factory:
|
208
|
+
factory: Caller[..., T]
|
271
209
|
classes: Iterable[InputType[T]]
|
272
210
|
injectable_factory: InjectableFactory[T]
|
273
211
|
mode: Mode
|
@@ -354,6 +292,11 @@ class Locator(Broker):
|
|
354
292
|
|
355
293
|
return self
|
356
294
|
|
295
|
+
@override
|
296
|
+
async def all_ready(self) -> None:
|
297
|
+
for injectable in self.__injectables:
|
298
|
+
await injectable.aget_instance()
|
299
|
+
|
357
300
|
def add_listener(self, listener: EventListener) -> Self:
|
358
301
|
self.__channel.add_listener(listener)
|
359
302
|
return self
|
@@ -466,18 +409,20 @@ class Module(Broker, EventListener):
|
|
466
409
|
yield from tuple(self.__modules)
|
467
410
|
yield self.__locator
|
468
411
|
|
469
|
-
def injectable[**P, T](
|
412
|
+
def injectable[**P, T](
|
470
413
|
self,
|
471
|
-
wrapped: Callable[P, T] | None = None,
|
414
|
+
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] | None = None,
|
472
415
|
/,
|
473
416
|
*,
|
474
417
|
cls: InjectableFactory[T] = SimpleInjectable,
|
475
418
|
inject: bool = True,
|
476
419
|
on: TypeInfo[T] = (),
|
477
420
|
mode: Mode | ModeStr = Mode.get_default(),
|
478
|
-
):
|
479
|
-
def decorator(
|
480
|
-
|
421
|
+
) -> Any:
|
422
|
+
def decorator(
|
423
|
+
wp: Callable[P, T] | Callable[P, Awaitable[T]],
|
424
|
+
) -> Callable[P, T] | Callable[P, Awaitable[T]]:
|
425
|
+
factory = _get_caller(self.make_injected_function(wp) if inject else wp)
|
481
426
|
classes = get_return_types(wp, on)
|
482
427
|
updater = Updater(
|
483
428
|
factory=factory,
|
@@ -492,12 +437,12 @@ class Module(Broker, EventListener):
|
|
492
437
|
|
493
438
|
singleton = partialmethod(injectable, cls=SingletonInjectable)
|
494
439
|
|
495
|
-
def should_be_injectable[T](self, wrapped: type[T] | None = None, /)
|
496
|
-
def decorator(wp
|
440
|
+
def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
|
441
|
+
def decorator(wp: type[T]) -> type[T]:
|
497
442
|
updater = Updater(
|
498
|
-
factory=wp,
|
443
|
+
factory=SyncCaller(wp),
|
499
444
|
classes=(wp,),
|
500
|
-
injectable_factory=ShouldBeInjectable
|
445
|
+
injectable_factory=lambda _: ShouldBeInjectable(wp),
|
501
446
|
mode=Mode.FALLBACK,
|
502
447
|
)
|
503
448
|
self.update(updater)
|
@@ -505,15 +450,15 @@ class Module(Broker, EventListener):
|
|
505
450
|
|
506
451
|
return decorator(wrapped) if wrapped else decorator
|
507
452
|
|
508
|
-
def constant[T](
|
453
|
+
def constant[T](
|
509
454
|
self,
|
510
455
|
wrapped: type[T] | None = None,
|
511
456
|
/,
|
512
457
|
*,
|
513
458
|
on: TypeInfo[T] = (),
|
514
459
|
mode: Mode | ModeStr = Mode.get_default(),
|
515
|
-
):
|
516
|
-
def decorator(wp
|
460
|
+
) -> Any:
|
461
|
+
def decorator(wp: type[T]) -> type[T]:
|
517
462
|
lazy_instance = Lazy(wp)
|
518
463
|
self.injectable(
|
519
464
|
lambda: ~lazy_instance,
|
@@ -545,8 +490,8 @@ class Module(Broker, EventListener):
|
|
545
490
|
)
|
546
491
|
return self
|
547
492
|
|
548
|
-
def inject[**P, T](self, wrapped: Callable[P, T] | None = None, /)
|
549
|
-
def decorator(wp
|
493
|
+
def inject[**P, T](self, wrapped: Callable[P, T] | None = None, /) -> Any:
|
494
|
+
def decorator(wp: Callable[P, T]) -> Callable[P, T]:
|
550
495
|
if isclass(wp):
|
551
496
|
wp.__init__ = self.inject(wp.__init__)
|
552
497
|
return wp
|
@@ -555,47 +500,135 @@ class Module(Broker, EventListener):
|
|
555
500
|
|
556
501
|
return decorator(wrapped) if wrapped else decorator
|
557
502
|
|
503
|
+
@overload
|
558
504
|
def make_injected_function[**P, T](
|
559
505
|
self,
|
560
506
|
wrapped: Callable[P, T],
|
561
507
|
/,
|
562
|
-
) ->
|
563
|
-
|
508
|
+
) -> SyncInjectedFunction[P, T]: ...
|
509
|
+
|
510
|
+
@overload
|
511
|
+
def make_injected_function[**P, T](
|
512
|
+
self,
|
513
|
+
wrapped: Callable[P, Awaitable[T]],
|
514
|
+
/,
|
515
|
+
) -> AsyncInjectedFunction[P, T]: ...
|
516
|
+
|
517
|
+
def make_injected_function(self, wrapped, /): # type: ignore[no-untyped-def]
|
518
|
+
metadata = InjectMetadata(wrapped)
|
564
519
|
|
565
|
-
@
|
520
|
+
@metadata.on_setup
|
566
521
|
def listen() -> None:
|
567
|
-
|
568
|
-
self.add_listener(
|
522
|
+
metadata.update(self)
|
523
|
+
self.add_listener(metadata)
|
524
|
+
|
525
|
+
if iscoroutinefunction(wrapped):
|
526
|
+
return AsyncInjectedFunction(metadata)
|
569
527
|
|
570
|
-
return
|
528
|
+
return SyncInjectedFunction(metadata)
|
529
|
+
|
530
|
+
async def afind_instance[T](self, cls: InputType[T]) -> T:
|
531
|
+
injectable = self[cls]
|
532
|
+
return await injectable.aget_instance()
|
571
533
|
|
572
534
|
def find_instance[T](self, cls: InputType[T]) -> T:
|
573
535
|
injectable = self[cls]
|
574
536
|
return injectable.get_instance()
|
575
537
|
|
538
|
+
@overload
|
539
|
+
async def aget_instance[T, Default](
|
540
|
+
self,
|
541
|
+
cls: InputType[T],
|
542
|
+
default: Default,
|
543
|
+
) -> T | Default: ...
|
544
|
+
|
545
|
+
@overload
|
546
|
+
async def aget_instance[T](
|
547
|
+
self,
|
548
|
+
cls: InputType[T],
|
549
|
+
default: None = ...,
|
550
|
+
) -> T | None: ...
|
551
|
+
|
552
|
+
async def aget_instance(self, cls, default=None): # type: ignore[no-untyped-def]
|
553
|
+
try:
|
554
|
+
return await self.afind_instance(cls)
|
555
|
+
except KeyError:
|
556
|
+
return default
|
557
|
+
|
558
|
+
@overload
|
576
559
|
def get_instance[T, Default](
|
577
560
|
self,
|
578
561
|
cls: InputType[T],
|
579
|
-
default: Default
|
580
|
-
) -> T | Default
|
562
|
+
default: Default,
|
563
|
+
) -> T | Default: ...
|
564
|
+
|
565
|
+
@overload
|
566
|
+
def get_instance[T](
|
567
|
+
self,
|
568
|
+
cls: InputType[T],
|
569
|
+
default: None = ...,
|
570
|
+
) -> T | None: ...
|
571
|
+
|
572
|
+
def get_instance(self, cls, default=None): # type: ignore[no-untyped-def]
|
581
573
|
try:
|
582
574
|
return self.find_instance(cls)
|
583
575
|
except KeyError:
|
584
576
|
return default
|
585
577
|
|
578
|
+
@overload
|
579
|
+
def aget_lazy_instance[T, Default](
|
580
|
+
self,
|
581
|
+
cls: InputType[T],
|
582
|
+
default: Default,
|
583
|
+
*,
|
584
|
+
cache: bool = ...,
|
585
|
+
) -> Awaitable[T | Default]: ...
|
586
|
+
|
587
|
+
@overload
|
588
|
+
def aget_lazy_instance[T](
|
589
|
+
self,
|
590
|
+
cls: InputType[T],
|
591
|
+
default: None = ...,
|
592
|
+
*,
|
593
|
+
cache: bool = ...,
|
594
|
+
) -> Awaitable[T | None]: ...
|
595
|
+
|
596
|
+
def aget_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
|
597
|
+
if cache:
|
598
|
+
coroutine = self.aget_instance(cls, default)
|
599
|
+
return asyncio.ensure_future(coroutine)
|
600
|
+
|
601
|
+
function = self.make_injected_function(lambda instance=default: instance)
|
602
|
+
metadata = function.__inject_metadata__
|
603
|
+
metadata.set_owner(cls)
|
604
|
+
return SimpleAwaitable(metadata.acall)
|
605
|
+
|
606
|
+
@overload
|
586
607
|
def get_lazy_instance[T, Default](
|
587
608
|
self,
|
588
609
|
cls: InputType[T],
|
589
|
-
default: Default
|
610
|
+
default: Default,
|
590
611
|
*,
|
591
|
-
cache: bool =
|
592
|
-
) -> Invertible[T | Default
|
612
|
+
cache: bool = ...,
|
613
|
+
) -> Invertible[T | Default]: ...
|
614
|
+
|
615
|
+
@overload
|
616
|
+
def get_lazy_instance[T](
|
617
|
+
self,
|
618
|
+
cls: InputType[T],
|
619
|
+
default: None = ...,
|
620
|
+
*,
|
621
|
+
cache: bool = ...,
|
622
|
+
) -> Invertible[T | None]: ...
|
623
|
+
|
624
|
+
def get_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
|
593
625
|
if cache:
|
594
626
|
return Lazy(lambda: self.get_instance(cls, default))
|
595
627
|
|
596
|
-
function = self.
|
597
|
-
function.
|
598
|
-
|
628
|
+
function = self.make_injected_function(lambda instance=default: instance)
|
629
|
+
metadata = function.__inject_metadata__
|
630
|
+
metadata.set_owner(cls)
|
631
|
+
return SimpleInvertible(metadata.call)
|
599
632
|
|
600
633
|
def update[T](self, updater: Updater[T]) -> Self:
|
601
634
|
self.__locator.update(updater)
|
@@ -670,6 +703,11 @@ class Module(Broker, EventListener):
|
|
670
703
|
|
671
704
|
return self
|
672
705
|
|
706
|
+
@override
|
707
|
+
async def all_ready(self) -> None:
|
708
|
+
for broker in self.__brokers:
|
709
|
+
await broker.all_ready()
|
710
|
+
|
673
711
|
def add_logger(self, logger: Logger) -> Self:
|
674
712
|
self.__loggers.append(logger)
|
675
713
|
return self
|
@@ -730,6 +768,13 @@ class Module(Broker, EventListener):
|
|
730
768
|
return cls.from_name("__default__")
|
731
769
|
|
732
770
|
|
771
|
+
def mod(name: str | None = None, /) -> Module:
|
772
|
+
if name is None:
|
773
|
+
return Module.default()
|
774
|
+
|
775
|
+
return Module.from_name(name)
|
776
|
+
|
777
|
+
|
733
778
|
"""
|
734
779
|
InjectedFunction
|
735
780
|
"""
|
@@ -744,7 +789,13 @@ class Dependencies:
|
|
744
789
|
|
745
790
|
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
746
791
|
for name, injectable in self.mapping.items():
|
747
|
-
|
792
|
+
instance = injectable.get_instance()
|
793
|
+
yield name, instance
|
794
|
+
|
795
|
+
async def __aiter__(self) -> AsyncIterator[tuple[str, Any]]:
|
796
|
+
for name, injectable in self.mapping.items():
|
797
|
+
instance = await injectable.aget_instance()
|
798
|
+
yield name, instance
|
748
799
|
|
749
800
|
@property
|
750
801
|
def are_resolved(self) -> bool:
|
@@ -753,9 +804,11 @@ class Dependencies:
|
|
753
804
|
|
754
805
|
return bool(self)
|
755
806
|
|
756
|
-
|
757
|
-
|
758
|
-
|
807
|
+
async def aget_arguments(self) -> dict[str, Any]:
|
808
|
+
return {key: value async for key, value in self}
|
809
|
+
|
810
|
+
def get_arguments(self) -> dict[str, Any]:
|
811
|
+
return dict(self)
|
759
812
|
|
760
813
|
@classmethod
|
761
814
|
def from_mapping(cls, mapping: Mapping[str, Injectable[Any]]) -> Self:
|
@@ -810,7 +863,7 @@ class Arguments(NamedTuple):
|
|
810
863
|
kwargs: Mapping[str, Any]
|
811
864
|
|
812
865
|
|
813
|
-
class
|
866
|
+
class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
814
867
|
__slots__ = (
|
815
868
|
"__dependencies",
|
816
869
|
"__owner",
|
@@ -831,11 +884,6 @@ class Injected[**P, T](EventListener):
|
|
831
884
|
self.__setup_queue = Queue()
|
832
885
|
self.__wrapped = wrapped
|
833
886
|
|
834
|
-
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
835
|
-
self.__setup()
|
836
|
-
arguments = self.bind(args, kwargs)
|
837
|
-
return self.wrapped(*arguments.args, **arguments.kwargs)
|
838
|
-
|
839
887
|
@property
|
840
888
|
def signature(self) -> Signature:
|
841
889
|
with suppress(AttributeError):
|
@@ -851,22 +899,33 @@ class Injected[**P, T](EventListener):
|
|
851
899
|
def wrapped(self) -> Callable[P, T]:
|
852
900
|
return self.__wrapped
|
853
901
|
|
902
|
+
async def abind(
|
903
|
+
self,
|
904
|
+
args: Iterable[Any] = (),
|
905
|
+
kwargs: Mapping[str, Any] | None = None,
|
906
|
+
) -> Arguments:
|
907
|
+
additional_arguments = await self.__dependencies.aget_arguments()
|
908
|
+
return self.__bind(args, kwargs, additional_arguments)
|
909
|
+
|
854
910
|
def bind(
|
855
911
|
self,
|
856
912
|
args: Iterable[Any] = (),
|
857
913
|
kwargs: Mapping[str, Any] | None = None,
|
858
914
|
) -> Arguments:
|
859
|
-
|
860
|
-
|
915
|
+
additional_arguments = self.__dependencies.get_arguments()
|
916
|
+
return self.__bind(args, kwargs, additional_arguments)
|
861
917
|
|
862
|
-
|
863
|
-
|
918
|
+
@override
|
919
|
+
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
920
|
+
self.__setup()
|
921
|
+
arguments = await self.abind(args, kwargs)
|
922
|
+
return self.wrapped(*arguments.args, **arguments.kwargs)
|
864
923
|
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
)
|
869
|
-
return
|
924
|
+
@override
|
925
|
+
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
926
|
+
self.__setup()
|
927
|
+
arguments = self.bind(args, kwargs)
|
928
|
+
return self.wrapped(*arguments.args, **arguments.kwargs)
|
870
929
|
|
871
930
|
def set_owner(self, owner: type) -> Self:
|
872
931
|
if self.__dependencies.are_resolved:
|
@@ -885,8 +944,8 @@ class Injected[**P, T](EventListener):
|
|
885
944
|
self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
|
886
945
|
return self
|
887
946
|
|
888
|
-
def on_setup[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /)
|
889
|
-
def decorator(wp
|
947
|
+
def on_setup[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
|
948
|
+
def decorator(wp: Callable[_P, _T]) -> Callable[_P, _T]:
|
890
949
|
queue = self.__setup_queue
|
891
950
|
|
892
951
|
if queue is None:
|
@@ -908,6 +967,22 @@ class Injected[**P, T](EventListener):
|
|
908
967
|
yield
|
909
968
|
self.update(event.module)
|
910
969
|
|
970
|
+
def __bind(
|
971
|
+
self,
|
972
|
+
args: Iterable[Any],
|
973
|
+
kwargs: Mapping[str, Any] | None,
|
974
|
+
additional_arguments: dict[str, Any] | None,
|
975
|
+
) -> Arguments:
|
976
|
+
if kwargs is None:
|
977
|
+
kwargs = {}
|
978
|
+
|
979
|
+
if not additional_arguments:
|
980
|
+
return Arguments(args, kwargs)
|
981
|
+
|
982
|
+
bound = self.signature.bind_partial(*args, **kwargs)
|
983
|
+
bound.arguments = bound.arguments | additional_arguments | bound.arguments
|
984
|
+
return Arguments(bound.args, bound.kwargs)
|
985
|
+
|
911
986
|
def __close_setup_queue(self) -> None:
|
912
987
|
self.__setup_queue = None
|
913
988
|
|
@@ -930,25 +1005,26 @@ class Injected[**P, T](EventListener):
|
|
930
1005
|
self.__close_setup_queue()
|
931
1006
|
|
932
1007
|
|
933
|
-
class InjectedFunction[**P, T]:
|
934
|
-
__slots__ = ("__dict__", "
|
1008
|
+
class InjectedFunction[**P, T](ABC):
|
1009
|
+
__slots__ = ("__dict__", "__inject_metadata__")
|
935
1010
|
|
936
|
-
|
1011
|
+
__inject_metadata__: InjectMetadata[P, T]
|
937
1012
|
|
938
|
-
def __init__(self,
|
939
|
-
update_wrapper(self,
|
940
|
-
self.
|
1013
|
+
def __init__(self, metadata: InjectMetadata[P, T]) -> None:
|
1014
|
+
update_wrapper(self, metadata.wrapped)
|
1015
|
+
self.__inject_metadata__ = metadata
|
941
1016
|
|
942
1017
|
@override
|
943
1018
|
def __repr__(self) -> str: # pragma: no cover
|
944
|
-
return repr(self.
|
1019
|
+
return repr(self.__inject_metadata__.wrapped)
|
945
1020
|
|
946
1021
|
@override
|
947
1022
|
def __str__(self) -> str: # pragma: no cover
|
948
|
-
return str(self.
|
1023
|
+
return str(self.__inject_metadata__.wrapped)
|
949
1024
|
|
1025
|
+
@abstractmethod
|
950
1026
|
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
951
|
-
|
1027
|
+
raise NotImplementedError
|
952
1028
|
|
953
1029
|
def __get__(
|
954
1030
|
self,
|
@@ -961,4 +1037,43 @@ class InjectedFunction[**P, T]:
|
|
961
1037
|
return MethodType(self, instance)
|
962
1038
|
|
963
1039
|
def __set_name__(self, owner: type, name: str) -> None:
|
964
|
-
self.
|
1040
|
+
self.__inject_metadata__.set_owner(owner)
|
1041
|
+
|
1042
|
+
|
1043
|
+
class AsyncInjectedFunction[**P, T](InjectedFunction[P, Awaitable[T]]):
|
1044
|
+
__slots__ = ()
|
1045
|
+
|
1046
|
+
@override
|
1047
|
+
async def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
1048
|
+
return await (await self.__inject_metadata__.acall(*args, **kwargs))
|
1049
|
+
|
1050
|
+
|
1051
|
+
class SyncInjectedFunction[**P, T](InjectedFunction[P, T]):
|
1052
|
+
__slots__ = ()
|
1053
|
+
|
1054
|
+
@override
|
1055
|
+
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
1056
|
+
return self.__inject_metadata__.call(*args, **kwargs)
|
1057
|
+
|
1058
|
+
|
1059
|
+
def _is_coroutine_function[**P, T](
|
1060
|
+
function: Callable[P, T] | Callable[P, Awaitable[T]],
|
1061
|
+
) -> TypeGuard[Callable[P, Awaitable[T]]]:
|
1062
|
+
if iscoroutinefunction(function):
|
1063
|
+
return True
|
1064
|
+
|
1065
|
+
elif isclass(function):
|
1066
|
+
return False
|
1067
|
+
|
1068
|
+
call = getattr(function, "__call__", None)
|
1069
|
+
return iscoroutinefunction(call)
|
1070
|
+
|
1071
|
+
|
1072
|
+
def _get_caller[**P, T](function: Callable[P, T]) -> Caller[P, T]:
|
1073
|
+
if _is_coroutine_function(function):
|
1074
|
+
return AsyncCaller(function)
|
1075
|
+
|
1076
|
+
elif isinstance(function, InjectedFunction):
|
1077
|
+
return function.__inject_metadata__
|
1078
|
+
|
1079
|
+
return SyncCaller(function)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from collections.abc import
|
1
|
+
from collections.abc import Awaitable
|
2
2
|
from types import GenericAlias
|
3
3
|
from typing import Any, TypeAliasType
|
4
4
|
|
@@ -28,18 +28,29 @@ def Inject[T]( # noqa: N802
|
|
28
28
|
|
29
29
|
|
30
30
|
class InjectionDependency[T]:
|
31
|
-
__slots__ = ("
|
31
|
+
__slots__ = ("__class", "__lazy_instance", "__module")
|
32
32
|
|
33
|
-
__call__: Callable[[], T]
|
34
33
|
__class: type[T] | TypeAliasType | GenericAlias
|
34
|
+
__lazy_instance: Awaitable[T]
|
35
35
|
__module: Module
|
36
36
|
|
37
|
-
def __init__(
|
38
|
-
|
39
|
-
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
cls: type[T] | TypeAliasType | GenericAlias,
|
40
|
+
module: Module,
|
41
|
+
) -> None:
|
40
42
|
self.__class = cls
|
43
|
+
self.__lazy_instance = module.aget_lazy_instance(cls, default=NotImplemented)
|
41
44
|
self.__module = module
|
42
45
|
|
46
|
+
async def __call__(self) -> T:
|
47
|
+
instance = await self.__lazy_instance
|
48
|
+
|
49
|
+
if instance is NotImplemented:
|
50
|
+
raise InjectionError(f"`{self.__class}` is an unknown dependency.")
|
51
|
+
|
52
|
+
return instance
|
53
|
+
|
43
54
|
def __eq__(self, other: Any) -> bool:
|
44
55
|
if isinstance(other, type(self)):
|
45
56
|
return self.__key == other.__key
|
@@ -52,9 +63,3 @@ class InjectionDependency[T]:
|
|
52
63
|
@property
|
53
64
|
def __key(self) -> tuple[type[T] | TypeAliasType | GenericAlias, Module]:
|
54
65
|
return self.__class, self.__module
|
55
|
-
|
56
|
-
def __ensure(self, instance: T) -> T:
|
57
|
-
if instance is NotImplemented:
|
58
|
-
raise InjectionError(f"`{self.__class}` is an unknown dependency.")
|
59
|
-
|
60
|
-
return instance
|
@@ -8,7 +8,7 @@ test_constant = _.constant
|
|
8
8
|
test_injectable = _.injectable
|
9
9
|
test_singleton = _.singleton
|
10
10
|
|
11
|
-
def load_test_profile(*
|
11
|
+
def load_test_profile(*names: str) -> ContextManager[None]:
|
12
12
|
"""
|
13
13
|
Context manager or decorator for temporary use test module.
|
14
14
|
"""
|
@@ -1,11 +1,11 @@
|
|
1
|
-
[
|
1
|
+
[project]
|
2
2
|
name = "python-injection"
|
3
|
-
version = "0.
|
3
|
+
version = "0.12.0"
|
4
4
|
description = "Fast and easy dependency injection framework."
|
5
5
|
license = "MIT"
|
6
|
-
authors = ["remimd"]
|
7
6
|
readme = "README.md"
|
8
|
-
|
7
|
+
requires-python = ">=3.12, <4"
|
8
|
+
authors = [{ name = "remimd" }]
|
9
9
|
keywords = ["dependencies", "dependency", "inject", "injection"]
|
10
10
|
classifiers = [
|
11
11
|
"Development Status :: 4 - Beta",
|
@@ -21,17 +21,19 @@ classifiers = [
|
|
21
21
|
"Natural Language :: English",
|
22
22
|
"Typing :: Typed",
|
23
23
|
]
|
24
|
-
|
24
|
+
dependencies = []
|
25
25
|
|
26
|
-
[
|
27
|
-
|
26
|
+
[project.urls]
|
27
|
+
repository = "https://github.com/100nm/python-injection"
|
28
|
+
|
29
|
+
[tool.poetry]
|
30
|
+
packages = [{ include = "injection" }]
|
28
31
|
|
29
32
|
[tool.poetry.group.dev.dependencies]
|
30
33
|
mypy = "*"
|
31
34
|
ruff = "*"
|
32
35
|
|
33
36
|
[tool.poetry.group.test.dependencies]
|
34
|
-
blacksheep = { version = "*", optional = true }
|
35
37
|
fastapi = "*"
|
36
38
|
httpx = "*"
|
37
39
|
pydantic = "*"
|
@@ -70,7 +72,6 @@ exclude = [
|
|
70
72
|
"tests/",
|
71
73
|
"bench.py",
|
72
74
|
"conftest.py",
|
73
|
-
"injection/integrations/blacksheep.py",
|
74
75
|
]
|
75
76
|
follow_imports = "silent"
|
76
77
|
no_implicit_reexport = true
|
@@ -85,7 +86,7 @@ warn_required_dynamic_aliases = true
|
|
85
86
|
|
86
87
|
[tool.pytest.ini_options]
|
87
88
|
python_files = "test_*.py"
|
88
|
-
addopts = "--tb short --cov ./ --cov-report term-missing:skip-covered
|
89
|
+
addopts = "--tb short --cov ./ --cov-report term-missing:skip-covered"
|
89
90
|
asyncio_default_fixture_loop_scope = "session"
|
90
91
|
asyncio_mode = "auto"
|
91
92
|
testpaths = "**/tests/"
|
@@ -1,34 +0,0 @@
|
|
1
|
-
from typing import Any, override
|
2
|
-
|
3
|
-
from injection import Module, mod
|
4
|
-
from injection.integrations import _is_installed
|
5
|
-
|
6
|
-
__all__ = ("InjectionServices",)
|
7
|
-
|
8
|
-
if _is_installed("blacksheep", __name__):
|
9
|
-
from rodi import ContainerProtocol
|
10
|
-
|
11
|
-
|
12
|
-
class InjectionServices(ContainerProtocol):
|
13
|
-
"""
|
14
|
-
BlackSheep dependency injection container implemented with `python-injection`.
|
15
|
-
"""
|
16
|
-
|
17
|
-
__slots__ = ("__module",)
|
18
|
-
|
19
|
-
__module: Module
|
20
|
-
|
21
|
-
def __init__(self, module: Module | None = None) -> None:
|
22
|
-
self.__module = module or mod()
|
23
|
-
|
24
|
-
@override
|
25
|
-
def __contains__(self, item: Any) -> bool:
|
26
|
-
return item in self.__module
|
27
|
-
|
28
|
-
@override
|
29
|
-
def register(self, obj_type: type | Any, *args: Any, **kwargs: Any) -> None:
|
30
|
-
self.__module.injectable(obj_type)
|
31
|
-
|
32
|
-
@override
|
33
|
-
def resolve[T](self, obj_type: type[T] | Any, *args: Any, **kwargs: Any) -> T:
|
34
|
-
return self.__module.find_instance(obj_type)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|