python-injection 0.12.3__py3-none-any.whl → 0.13.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- injection/__init__.py +5 -0
- injection/__init__.pyi +59 -48
- injection/_core/common/asynchronous.py +12 -11
- injection/_core/common/event.py +1 -1
- injection/_core/common/key.py +5 -0
- injection/_core/common/type.py +33 -2
- injection/_core/injectables.py +103 -22
- injection/_core/module.py +109 -46
- injection/_core/scope.py +215 -0
- injection/exceptions.py +12 -0
- injection/integrations/fastapi.py +5 -30
- injection/testing/__init__.py +2 -0
- injection/testing/__init__.pyi +10 -7
- injection/utils.py +7 -2
- {python_injection-0.12.3.dist-info → python_injection-0.13.0.dist-info}/METADATA +14 -15
- python_injection-0.13.0.dist-info/RECORD +25 -0
- {python_injection-0.12.3.dist-info → python_injection-0.13.0.dist-info}/WHEEL +1 -1
- injection/_core/common/threading.py +0 -11
- python_injection-0.12.3.dist-info/RECORD +0 -24
injection/__init__.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ._core.descriptors import LazyInstance
|
2
2
|
from ._core.injectables import Injectable
|
3
3
|
from ._core.module import Mode, Module, Priority, mod
|
4
|
+
from ._core.scope import adefine_scope, define_scope
|
4
5
|
|
5
6
|
__all__ = (
|
6
7
|
"Injectable",
|
@@ -8,16 +9,19 @@ __all__ = (
|
|
8
9
|
"Mode",
|
9
10
|
"Module",
|
10
11
|
"Priority",
|
12
|
+
"adefine_scope",
|
11
13
|
"afind_instance",
|
12
14
|
"aget_instance",
|
13
15
|
"aget_lazy_instance",
|
14
16
|
"constant",
|
17
|
+
"define_scope",
|
15
18
|
"find_instance",
|
16
19
|
"get_instance",
|
17
20
|
"get_lazy_instance",
|
18
21
|
"inject",
|
19
22
|
"injectable",
|
20
23
|
"mod",
|
24
|
+
"scoped",
|
21
25
|
"set_constant",
|
22
26
|
"should_be_injectable",
|
23
27
|
"singleton",
|
@@ -32,6 +36,7 @@ get_instance = mod().get_instance
|
|
32
36
|
get_lazy_instance = mod().get_lazy_instance
|
33
37
|
inject = mod().inject
|
34
38
|
injectable = mod().injectable
|
39
|
+
scoped = mod().scoped
|
35
40
|
set_constant = mod().set_constant
|
36
41
|
should_be_injectable = mod().should_be_injectable
|
37
42
|
singleton = mod().singleton
|
injection/__init__.pyi
CHANGED
@@ -1,17 +1,9 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from collections.abc import Awaitable, Callable
|
3
|
-
from contextlib import
|
2
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
3
|
+
from contextlib import asynccontextmanager, contextmanager
|
4
4
|
from enum import Enum
|
5
5
|
from logging import Logger
|
6
|
-
from typing import
|
7
|
-
Any,
|
8
|
-
ContextManager,
|
9
|
-
Protocol,
|
10
|
-
Self,
|
11
|
-
final,
|
12
|
-
overload,
|
13
|
-
runtime_checkable,
|
14
|
-
)
|
6
|
+
from typing import Any, Final, Protocol, Self, final, overload, runtime_checkable
|
15
7
|
|
16
8
|
from ._core.common.invertible import Invertible as _Invertible
|
17
9
|
from ._core.common.type import InputType as _InputType
|
@@ -19,27 +11,47 @@ from ._core.common.type import TypeInfo as _TypeInfo
|
|
19
11
|
from ._core.module import InjectableFactory as _InjectableFactory
|
20
12
|
from ._core.module import ModeStr, PriorityStr
|
21
13
|
|
22
|
-
|
14
|
+
__MODULE: Final[Module] = ...
|
23
15
|
|
24
|
-
afind_instance =
|
25
|
-
aget_instance =
|
26
|
-
aget_lazy_instance =
|
27
|
-
constant =
|
28
|
-
find_instance =
|
29
|
-
get_instance =
|
30
|
-
get_lazy_instance =
|
31
|
-
inject =
|
32
|
-
injectable =
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
del _
|
16
|
+
afind_instance = __MODULE.afind_instance
|
17
|
+
aget_instance = __MODULE.aget_instance
|
18
|
+
aget_lazy_instance = __MODULE.aget_lazy_instance
|
19
|
+
constant = __MODULE.constant
|
20
|
+
find_instance = __MODULE.find_instance
|
21
|
+
get_instance = __MODULE.get_instance
|
22
|
+
get_lazy_instance = __MODULE.get_lazy_instance
|
23
|
+
inject = __MODULE.inject
|
24
|
+
injectable = __MODULE.injectable
|
25
|
+
scoped = __MODULE.scoped
|
26
|
+
set_constant = __MODULE.set_constant
|
27
|
+
should_be_injectable = __MODULE.should_be_injectable
|
28
|
+
singleton = __MODULE.singleton
|
38
29
|
|
30
|
+
@asynccontextmanager
|
31
|
+
def adefine_scope(name: str, *, shared: bool = ...) -> AsyncIterator[None]: ...
|
32
|
+
@contextmanager
|
33
|
+
def define_scope(name: str, *, shared: bool = ...) -> Iterator[None]: ...
|
39
34
|
def mod(name: str = ..., /) -> Module:
|
40
35
|
"""
|
41
36
|
Short syntax for `Module.from_name`.
|
42
37
|
"""
|
38
|
+
@runtime_checkable
|
39
|
+
class Injectable[T](Protocol):
|
40
|
+
@property
|
41
|
+
def is_locked(self) -> bool: ...
|
42
|
+
def unlock(self) -> None: ...
|
43
|
+
@abstractmethod
|
44
|
+
async def aget_instance(self) -> T: ...
|
45
|
+
@abstractmethod
|
46
|
+
def get_instance(self) -> T: ...
|
47
|
+
|
48
|
+
class LazyInstance[T]:
|
49
|
+
def __init__(self, cls: _InputType[T], module: Module = ...) -> None: ...
|
50
|
+
@overload
|
51
|
+
def __get__(self, instance: object, owner: type | None = ...) -> T: ...
|
52
|
+
@overload
|
53
|
+
def __get__(self, instance: None = ..., owner: type | None = ...) -> Self: ...
|
54
|
+
|
43
55
|
@final
|
44
56
|
class Module:
|
45
57
|
"""
|
@@ -94,6 +106,21 @@ class Module:
|
|
94
106
|
always be the same.
|
95
107
|
"""
|
96
108
|
|
109
|
+
def scoped[**P, T](
|
110
|
+
self,
|
111
|
+
scope_name: str,
|
112
|
+
/,
|
113
|
+
*,
|
114
|
+
inject: bool = ...,
|
115
|
+
on: _TypeInfo[T] = (),
|
116
|
+
mode: Mode | ModeStr = ...,
|
117
|
+
) -> Any:
|
118
|
+
"""
|
119
|
+
Decorator applicable to a class or function or generator function. It is used
|
120
|
+
to indicate how the scoped instance will be constructed. At injection time, the
|
121
|
+
injected instance is retrieved from the scope.
|
122
|
+
"""
|
123
|
+
|
97
124
|
def should_be_injectable[T](self, wrapped: type[T] = ..., /) -> Any:
|
98
125
|
"""
|
99
126
|
Decorator applicable to a class. It is used to specify whether an injectable
|
@@ -233,12 +260,13 @@ class Module:
|
|
233
260
|
Function to remove a module in use.
|
234
261
|
"""
|
235
262
|
|
263
|
+
@contextmanager
|
236
264
|
def use_temporarily(
|
237
265
|
self,
|
238
266
|
module: Module,
|
239
267
|
*,
|
240
268
|
priority: Priority | PriorityStr = ...,
|
241
|
-
) ->
|
269
|
+
) -> Iterator[None]:
|
242
270
|
"""
|
243
271
|
Context manager or decorator for temporary use of a module.
|
244
272
|
"""
|
@@ -275,30 +303,13 @@ class Module:
|
|
275
303
|
Class method for getting the default module.
|
276
304
|
"""
|
277
305
|
|
278
|
-
@final
|
279
|
-
class Priority(Enum):
|
280
|
-
LOW = ...
|
281
|
-
HIGH = ...
|
282
|
-
|
283
|
-
@runtime_checkable
|
284
|
-
class Injectable[T](Protocol):
|
285
|
-
@property
|
286
|
-
def is_locked(self) -> bool: ...
|
287
|
-
def unlock(self) -> None: ...
|
288
|
-
@abstractmethod
|
289
|
-
async def aget_instance(self) -> T: ...
|
290
|
-
@abstractmethod
|
291
|
-
def get_instance(self) -> T: ...
|
292
|
-
|
293
306
|
@final
|
294
307
|
class Mode(Enum):
|
295
308
|
FALLBACK = ...
|
296
309
|
NORMAL = ...
|
297
310
|
OVERRIDE = ...
|
298
311
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
@overload
|
304
|
-
def __get__(self, instance: None = ..., owner: type | None = ...) -> Self: ...
|
312
|
+
@final
|
313
|
+
class Priority(Enum):
|
314
|
+
LOW = ...
|
315
|
+
HIGH = ...
|
@@ -1,10 +1,19 @@
|
|
1
1
|
import asyncio
|
2
2
|
from abc import abstractmethod
|
3
|
-
from collections.abc import Awaitable, Callable, Generator
|
3
|
+
from collections.abc import Awaitable, Callable, Coroutine, Generator
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from typing import Any, Protocol, runtime_checkable
|
6
6
|
|
7
7
|
|
8
|
+
def run_sync[T](coroutine: Coroutine[Any, Any, T]) -> T:
|
9
|
+
loop = asyncio.get_event_loop()
|
10
|
+
|
11
|
+
try:
|
12
|
+
return loop.run_until_complete(coroutine)
|
13
|
+
finally:
|
14
|
+
coroutine.close()
|
15
|
+
|
16
|
+
|
8
17
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
9
18
|
class SimpleAwaitable[T](Awaitable[T]):
|
10
19
|
callable: Callable[..., Awaitable[T]]
|
@@ -28,21 +37,13 @@ class Caller[**P, T](Protocol):
|
|
28
37
|
|
29
38
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
30
39
|
class AsyncCaller[**P, T](Caller[P, T]):
|
31
|
-
callable: Callable[P,
|
40
|
+
callable: Callable[P, Coroutine[Any, Any, T]]
|
32
41
|
|
33
42
|
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
34
43
|
return await self.callable(*args, **kwargs)
|
35
44
|
|
36
45
|
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
37
|
-
|
38
|
-
|
39
|
-
if loop.is_running():
|
40
|
-
raise RuntimeError(
|
41
|
-
"Can't call an asynchronous function in a synchronous context."
|
42
|
-
)
|
43
|
-
|
44
|
-
coroutine = self.callable(*args, **kwargs)
|
45
|
-
return loop.run_until_complete(coroutine)
|
46
|
+
return run_sync(self.callable(*args, **kwargs))
|
46
47
|
|
47
48
|
|
48
49
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
injection/_core/common/event.py
CHANGED
@@ -25,7 +25,7 @@ class EventChannel:
|
|
25
25
|
@contextmanager
|
26
26
|
def dispatch(self, event: Event) -> Iterator[None]:
|
27
27
|
with ExitStack() as stack:
|
28
|
-
for listener in
|
28
|
+
for listener in self.__listeners:
|
29
29
|
context_manager = listener.on_event(event)
|
30
30
|
|
31
31
|
if context_manager is None:
|
injection/_core/common/type.py
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
from collections.abc import
|
1
|
+
from collections.abc import (
|
2
|
+
AsyncGenerator,
|
3
|
+
AsyncIterable,
|
4
|
+
AsyncIterator,
|
5
|
+
Callable,
|
6
|
+
Generator,
|
7
|
+
Iterable,
|
8
|
+
Iterator,
|
9
|
+
)
|
2
10
|
from inspect import isfunction
|
3
11
|
from types import GenericAlias, UnionType
|
4
12
|
from typing import (
|
@@ -23,7 +31,7 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
|
|
23
31
|
):
|
24
32
|
inner_args = arg
|
25
33
|
|
26
|
-
elif isfunction(arg) and (return_type :=
|
34
|
+
elif isfunction(arg) and (return_type := get_return_hint(arg)):
|
27
35
|
inner_args = (return_type,)
|
28
36
|
|
29
37
|
else:
|
@@ -33,6 +41,29 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
|
|
33
41
|
yield from get_return_types(*inner_args)
|
34
42
|
|
35
43
|
|
44
|
+
def get_return_hint[T](function: Callable[..., T]) -> InputType[T] | None:
|
45
|
+
return get_type_hints(function).get("return")
|
46
|
+
|
47
|
+
|
48
|
+
def get_yield_hint[T](
|
49
|
+
function: Callable[..., Iterator[T]] | Callable[..., AsyncIterator[T]],
|
50
|
+
) -> InputType[T] | None:
|
51
|
+
return_type = get_return_hint(function)
|
52
|
+
|
53
|
+
if get_origin(return_type) not in {
|
54
|
+
AsyncGenerator,
|
55
|
+
AsyncIterable,
|
56
|
+
AsyncIterator,
|
57
|
+
Generator,
|
58
|
+
Iterable,
|
59
|
+
Iterator,
|
60
|
+
}:
|
61
|
+
return None
|
62
|
+
|
63
|
+
args = get_args(return_type)
|
64
|
+
return next(iter(args), None)
|
65
|
+
|
66
|
+
|
36
67
|
def standardize_types(
|
37
68
|
*types: InputType[Any],
|
38
69
|
with_origin: bool = False,
|
injection/_core/injectables.py
CHANGED
@@ -2,10 +2,18 @@ from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import MutableMapping
|
3
3
|
from contextlib import suppress
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from typing import
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
from typing import (
|
6
|
+
Any,
|
7
|
+
AsyncContextManager,
|
8
|
+
ClassVar,
|
9
|
+
ContextManager,
|
10
|
+
NoReturn,
|
11
|
+
Protocol,
|
12
|
+
runtime_checkable,
|
13
|
+
)
|
14
|
+
|
15
|
+
from injection._core.common.asynchronous import Caller, run_sync
|
16
|
+
from injection._core.scope import Scope, get_active_scopes, get_scope
|
9
17
|
from injection.exceptions import InjectionError
|
10
18
|
|
11
19
|
|
@@ -50,41 +58,114 @@ class SingletonInjectable[T](BaseInjectable[T]):
|
|
50
58
|
__key: ClassVar[str] = "$instance"
|
51
59
|
|
52
60
|
@property
|
53
|
-
def
|
61
|
+
def is_locked(self) -> bool:
|
62
|
+
return self.__key in self.__cache
|
63
|
+
|
64
|
+
@property
|
65
|
+
def __cache(self) -> MutableMapping[str, Any]:
|
54
66
|
return self.__dict__
|
55
67
|
|
68
|
+
async def aget_instance(self) -> T:
|
69
|
+
cache = self.__cache
|
70
|
+
|
71
|
+
with suppress(KeyError):
|
72
|
+
return cache[self.__key]
|
73
|
+
|
74
|
+
instance = await self.factory.acall()
|
75
|
+
cache[self.__key] = instance
|
76
|
+
return instance
|
77
|
+
|
78
|
+
def get_instance(self) -> T:
|
79
|
+
cache = self.__cache
|
80
|
+
|
81
|
+
with suppress(KeyError):
|
82
|
+
return cache[self.__key]
|
83
|
+
|
84
|
+
instance = self.factory.call()
|
85
|
+
cache[self.__key] = instance
|
86
|
+
return instance
|
87
|
+
|
88
|
+
def unlock(self) -> None:
|
89
|
+
self.__cache.pop(self.__key, None)
|
90
|
+
|
91
|
+
|
92
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
93
|
+
class ScopedInjectable[R, T](Injectable[T], ABC):
|
94
|
+
factory: Caller[..., R]
|
95
|
+
scope_name: str
|
96
|
+
|
56
97
|
@property
|
57
98
|
def is_locked(self) -> bool:
|
58
|
-
return self.
|
99
|
+
return any(self in scope.cache for scope in get_active_scopes(self.scope_name))
|
59
100
|
|
60
|
-
|
61
|
-
|
101
|
+
@abstractmethod
|
102
|
+
async def abuild(self, scope: Scope) -> T:
|
103
|
+
raise NotImplementedError
|
104
|
+
|
105
|
+
@abstractmethod
|
106
|
+
def build(self, scope: Scope) -> T:
|
107
|
+
raise NotImplementedError
|
62
108
|
|
63
109
|
async def aget_instance(self) -> T:
|
64
|
-
|
65
|
-
return self.__check_instance()
|
110
|
+
scope = get_scope(self.scope_name)
|
66
111
|
|
67
|
-
with
|
68
|
-
|
69
|
-
self.__set_instance(instance)
|
112
|
+
with suppress(KeyError):
|
113
|
+
return scope.cache[self]
|
70
114
|
|
115
|
+
instance = await self.abuild(scope)
|
116
|
+
scope.cache[self] = instance
|
71
117
|
return instance
|
72
118
|
|
73
119
|
def get_instance(self) -> T:
|
74
|
-
|
75
|
-
return self.__check_instance()
|
120
|
+
scope = get_scope(self.scope_name)
|
76
121
|
|
77
|
-
with
|
78
|
-
|
79
|
-
self.__set_instance(instance)
|
122
|
+
with suppress(KeyError):
|
123
|
+
return scope.cache[self]
|
80
124
|
|
125
|
+
instance = self.build(scope)
|
126
|
+
scope.cache[self] = instance
|
81
127
|
return instance
|
82
128
|
|
83
|
-
def
|
84
|
-
|
129
|
+
def unlock(self) -> None:
|
130
|
+
if self.is_locked:
|
131
|
+
raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
|
132
|
+
|
133
|
+
|
134
|
+
class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
|
135
|
+
__slots__ = ()
|
136
|
+
|
137
|
+
async def abuild(self, scope: Scope) -> T:
|
138
|
+
cm = await self.factory.acall()
|
139
|
+
return await scope.aenter(cm)
|
140
|
+
|
141
|
+
def build(self, scope: Scope) -> T:
|
142
|
+
return run_sync(self.abuild(scope))
|
143
|
+
|
85
144
|
|
86
|
-
|
87
|
-
|
145
|
+
class CMScopedInjectable[T](ScopedInjectable[ContextManager[T], T]):
|
146
|
+
__slots__ = ()
|
147
|
+
|
148
|
+
async def abuild(self, scope: Scope) -> T:
|
149
|
+
cm = await self.factory.acall()
|
150
|
+
return scope.enter(cm)
|
151
|
+
|
152
|
+
def build(self, scope: Scope) -> T:
|
153
|
+
cm = self.factory.call()
|
154
|
+
return scope.enter(cm)
|
155
|
+
|
156
|
+
|
157
|
+
class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
158
|
+
__slots__ = ()
|
159
|
+
|
160
|
+
async def abuild(self, scope: Scope) -> T:
|
161
|
+
return await self.factory.acall()
|
162
|
+
|
163
|
+
def build(self, scope: Scope) -> T:
|
164
|
+
return self.factory.call()
|
165
|
+
|
166
|
+
def unlock(self) -> None:
|
167
|
+
for scope in get_active_scopes(self.scope_name):
|
168
|
+
scope.cache.pop(self, None)
|
88
169
|
|
89
170
|
|
90
171
|
@dataclass(repr=False, frozen=True, slots=True)
|
injection/_core/module.py
CHANGED
@@ -12,17 +12,25 @@ from collections.abc import (
|
|
12
12
|
Iterator,
|
13
13
|
Mapping,
|
14
14
|
)
|
15
|
-
from contextlib import contextmanager, suppress
|
15
|
+
from contextlib import asynccontextmanager, contextmanager, suppress
|
16
16
|
from dataclasses import dataclass, field
|
17
17
|
from enum import StrEnum
|
18
18
|
from functools import partialmethod, singledispatchmethod, update_wrapper
|
19
|
-
from inspect import
|
19
|
+
from inspect import (
|
20
|
+
Signature,
|
21
|
+
isasyncgenfunction,
|
22
|
+
isclass,
|
23
|
+
iscoroutinefunction,
|
24
|
+
isgeneratorfunction,
|
25
|
+
markcoroutinefunction,
|
26
|
+
)
|
20
27
|
from inspect import signature as inspect_signature
|
21
28
|
from logging import Logger, getLogger
|
22
29
|
from queue import Empty, Queue
|
23
30
|
from types import MethodType
|
24
31
|
from typing import (
|
25
32
|
Any,
|
33
|
+
AsyncContextManager,
|
26
34
|
ClassVar,
|
27
35
|
ContextManager,
|
28
36
|
Literal,
|
@@ -32,7 +40,6 @@ from typing import (
|
|
32
40
|
overload,
|
33
41
|
runtime_checkable,
|
34
42
|
)
|
35
|
-
from uuid import uuid4
|
36
43
|
|
37
44
|
from injection._core.common.asynchronous import (
|
38
45
|
AsyncCaller,
|
@@ -42,14 +49,22 @@ from injection._core.common.asynchronous import (
|
|
42
49
|
)
|
43
50
|
from injection._core.common.event import Event, EventChannel, EventListener
|
44
51
|
from injection._core.common.invertible import Invertible, SimpleInvertible
|
52
|
+
from injection._core.common.key import new_short_key
|
45
53
|
from injection._core.common.lazy import Lazy, LazyMapping
|
46
|
-
from injection._core.common.
|
47
|
-
|
54
|
+
from injection._core.common.type import (
|
55
|
+
InputType,
|
56
|
+
TypeInfo,
|
57
|
+
get_return_types,
|
58
|
+
get_yield_hint,
|
59
|
+
)
|
48
60
|
from injection._core.hook import Hook, apply_hooks
|
49
61
|
from injection._core.injectables import (
|
62
|
+
AsyncCMScopedInjectable,
|
63
|
+
CMScopedInjectable,
|
50
64
|
Injectable,
|
51
65
|
ShouldBeInjectable,
|
52
66
|
SimpleInjectable,
|
67
|
+
SimpleScopedInjectable,
|
53
68
|
SingletonInjectable,
|
54
69
|
)
|
55
70
|
from injection.exceptions import (
|
@@ -198,14 +213,12 @@ class Record[T](NamedTuple):
|
|
198
213
|
|
199
214
|
@dataclass(repr=False, eq=False, kw_only=True, slots=True)
|
200
215
|
class Updater[T]:
|
201
|
-
factory: Caller[..., T]
|
202
216
|
classes: Iterable[InputType[T]]
|
203
|
-
|
217
|
+
injectable: Injectable[T]
|
204
218
|
mode: Mode
|
205
219
|
|
206
220
|
def make_record(self) -> Record[T]:
|
207
|
-
|
208
|
-
return Record(injectable, self.mode)
|
221
|
+
return Record(self.injectable, self.mode)
|
209
222
|
|
210
223
|
|
211
224
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
@@ -262,7 +275,6 @@ class Locator(Broker):
|
|
262
275
|
def __injectables(self) -> frozenset[Injectable[Any]]:
|
263
276
|
return frozenset(record.injectable for record in self.__records.values())
|
264
277
|
|
265
|
-
@synchronized()
|
266
278
|
def update[T](self, updater: Updater[T]) -> Self:
|
267
279
|
updater = self.__update_preprocessing(updater)
|
268
280
|
record = updater.make_record()
|
@@ -276,7 +288,6 @@ class Locator(Broker):
|
|
276
288
|
|
277
289
|
return self
|
278
290
|
|
279
|
-
@synchronized()
|
280
291
|
def unlock(self) -> Self:
|
281
292
|
for injectable in self.__injectables:
|
282
293
|
injectable.unlock()
|
@@ -350,7 +361,7 @@ type PriorityStr = Literal["low", "high"]
|
|
350
361
|
|
351
362
|
@dataclass(eq=False, frozen=True, slots=True)
|
352
363
|
class Module(Broker, EventListener):
|
353
|
-
name: str = field(default_factory=lambda: f"anonymous@{
|
364
|
+
name: str = field(default_factory=lambda: f"anonymous@{new_short_key()}")
|
354
365
|
__channel: EventChannel = field(
|
355
366
|
default_factory=EventChannel,
|
356
367
|
init=False,
|
@@ -393,7 +404,7 @@ class Module(Broker, EventListener):
|
|
393
404
|
|
394
405
|
@property
|
395
406
|
def __brokers(self) -> Iterator[Broker]:
|
396
|
-
yield from
|
407
|
+
yield from self.__modules
|
397
408
|
yield self.__locator
|
398
409
|
|
399
410
|
def injectable[**P, T](
|
@@ -402,6 +413,7 @@ class Module(Broker, EventListener):
|
|
402
413
|
/,
|
403
414
|
*,
|
404
415
|
cls: InjectableFactory[T] = SimpleInjectable,
|
416
|
+
ignore_type_hint: bool = False,
|
405
417
|
inject: bool = True,
|
406
418
|
on: TypeInfo[T] = (),
|
407
419
|
mode: Mode | ModeStr = Mode.get_default(),
|
@@ -409,12 +421,11 @@ class Module(Broker, EventListener):
|
|
409
421
|
def decorator(
|
410
422
|
wp: Callable[P, T] | Callable[P, Awaitable[T]],
|
411
423
|
) -> Callable[P, T] | Callable[P, Awaitable[T]]:
|
412
|
-
factory =
|
413
|
-
|
424
|
+
factory = extract_caller(self.make_injected_function(wp) if inject else wp)
|
425
|
+
hints = on if ignore_type_hint else (wp, on)
|
414
426
|
updater = Updater(
|
415
|
-
|
416
|
-
|
417
|
-
injectable_factory=cls,
|
427
|
+
classes=get_return_types(hints),
|
428
|
+
injectable=cls(factory), # type: ignore[arg-type]
|
418
429
|
mode=Mode(mode),
|
419
430
|
)
|
420
431
|
self.update(updater)
|
@@ -424,12 +435,66 @@ class Module(Broker, EventListener):
|
|
424
435
|
|
425
436
|
singleton = partialmethod(injectable, cls=SingletonInjectable)
|
426
437
|
|
438
|
+
def scoped[**P, T](
|
439
|
+
self,
|
440
|
+
scope_name: str,
|
441
|
+
/,
|
442
|
+
*,
|
443
|
+
inject: bool = True,
|
444
|
+
on: TypeInfo[T] = (),
|
445
|
+
mode: Mode | ModeStr = Mode.get_default(),
|
446
|
+
) -> Any:
|
447
|
+
def decorator(
|
448
|
+
wp: Callable[P, T]
|
449
|
+
| Callable[P, Awaitable[T]]
|
450
|
+
| Callable[P, Iterator[T]]
|
451
|
+
| Callable[P, AsyncIterator[T]],
|
452
|
+
) -> (
|
453
|
+
Callable[P, T]
|
454
|
+
| Callable[P, Awaitable[T]]
|
455
|
+
| Callable[P, Iterator[T]]
|
456
|
+
| Callable[P, AsyncIterator[T]]
|
457
|
+
):
|
458
|
+
injectable_class: Callable[[Caller[P, Any], str], Injectable[T]]
|
459
|
+
wrapper: (
|
460
|
+
Callable[P, T]
|
461
|
+
| Callable[P, Awaitable[T]]
|
462
|
+
| Callable[P, ContextManager[T]]
|
463
|
+
| Callable[P, AsyncContextManager[T]]
|
464
|
+
)
|
465
|
+
|
466
|
+
if isasyncgenfunction(wp):
|
467
|
+
hint = get_yield_hint(wp)
|
468
|
+
injectable_class = AsyncCMScopedInjectable
|
469
|
+
wrapper = asynccontextmanager(wp)
|
470
|
+
|
471
|
+
elif isgeneratorfunction(wp):
|
472
|
+
hint = get_yield_hint(wp)
|
473
|
+
injectable_class = CMScopedInjectable
|
474
|
+
wrapper = contextmanager(wp)
|
475
|
+
|
476
|
+
else:
|
477
|
+
injectable_class = SimpleScopedInjectable
|
478
|
+
hint = wrapper = wp # type: ignore[assignment]
|
479
|
+
|
480
|
+
hints = on if hint is None else (hint, on)
|
481
|
+
self.injectable(
|
482
|
+
wrapper,
|
483
|
+
cls=lambda factory: injectable_class(factory, scope_name),
|
484
|
+
ignore_type_hint=True,
|
485
|
+
inject=inject,
|
486
|
+
on=hints,
|
487
|
+
mode=mode,
|
488
|
+
)
|
489
|
+
return wp
|
490
|
+
|
491
|
+
return decorator
|
492
|
+
|
427
493
|
def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
|
428
494
|
def decorator(wp: type[T]) -> type[T]:
|
429
495
|
updater = Updater(
|
430
|
-
factory=SyncCaller(wp),
|
431
496
|
classes=(wp,),
|
432
|
-
|
497
|
+
injectable=ShouldBeInjectable(wp),
|
433
498
|
mode=Mode.FALLBACK,
|
434
499
|
)
|
435
500
|
self.update(updater)
|
@@ -449,6 +514,7 @@ class Module(Broker, EventListener):
|
|
449
514
|
lazy_instance = Lazy(wp)
|
450
515
|
self.injectable(
|
451
516
|
lambda: ~lazy_instance,
|
517
|
+
ignore_type_hint=True,
|
452
518
|
inject=False,
|
453
519
|
on=(wp, on),
|
454
520
|
mode=mode,
|
@@ -465,14 +531,12 @@ class Module(Broker, EventListener):
|
|
465
531
|
alias: bool = False,
|
466
532
|
mode: Mode | ModeStr = Mode.get_default(),
|
467
533
|
) -> Self:
|
468
|
-
if
|
469
|
-
cls = type(instance)
|
470
|
-
on = (cls, on)
|
471
|
-
|
534
|
+
hints = on if alias else (type(instance), on)
|
472
535
|
self.injectable(
|
473
536
|
lambda: instance,
|
537
|
+
ignore_type_hint=True,
|
474
538
|
inject=False,
|
475
|
-
on=
|
539
|
+
on=hints,
|
476
540
|
mode=mode,
|
477
541
|
)
|
478
542
|
return self
|
@@ -670,8 +734,11 @@ class Module(Broker, EventListener):
|
|
670
734
|
priority: Priority | PriorityStr = Priority.get_default(),
|
671
735
|
) -> Iterator[None]:
|
672
736
|
self.use(module, priority=priority)
|
673
|
-
|
674
|
-
|
737
|
+
|
738
|
+
try:
|
739
|
+
yield
|
740
|
+
finally:
|
741
|
+
self.stop_using(module)
|
675
742
|
|
676
743
|
def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:
|
677
744
|
priority = Priority(priority)
|
@@ -682,7 +749,6 @@ class Module(Broker, EventListener):
|
|
682
749
|
|
683
750
|
return self
|
684
751
|
|
685
|
-
@synchronized()
|
686
752
|
def unlock(self) -> Self:
|
687
753
|
for broker in self.__brokers:
|
688
754
|
broker.unlock()
|
@@ -714,12 +780,14 @@ class Module(Broker, EventListener):
|
|
714
780
|
self.__check_locking()
|
715
781
|
|
716
782
|
with self.__channel.dispatch(event):
|
717
|
-
|
718
|
-
|
719
|
-
|
783
|
+
try:
|
784
|
+
yield
|
785
|
+
finally:
|
786
|
+
message = str(event)
|
787
|
+
self.__debug(message)
|
720
788
|
|
721
789
|
def __debug(self, message: object) -> None:
|
722
|
-
for logger in
|
790
|
+
for logger in self.__loggers:
|
723
791
|
logger.debug(message)
|
724
792
|
|
725
793
|
def __check_locking(self) -> None:
|
@@ -741,10 +809,8 @@ class Module(Broker, EventListener):
|
|
741
809
|
with suppress(KeyError):
|
742
810
|
return cls.__instances[name]
|
743
811
|
|
744
|
-
|
745
|
-
|
746
|
-
cls.__instances[name] = instance
|
747
|
-
|
812
|
+
instance = cls(name)
|
813
|
+
cls.__instances[name] = instance
|
748
814
|
return instance
|
749
815
|
|
750
816
|
@classmethod
|
@@ -873,10 +939,8 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
873
939
|
with suppress(AttributeError):
|
874
940
|
return self.__signature
|
875
941
|
|
876
|
-
|
877
|
-
|
878
|
-
self.__signature = signature
|
879
|
-
|
942
|
+
signature = inspect_signature(self.wrapped, eval_str=True)
|
943
|
+
self.__signature = signature
|
880
944
|
return signature
|
881
945
|
|
882
946
|
@property
|
@@ -921,7 +985,6 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
921
985
|
self.__owner = owner
|
922
986
|
return self
|
923
987
|
|
924
|
-
@synchronized()
|
925
988
|
def update(self, module: Module) -> Self:
|
926
989
|
self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
|
927
990
|
return self
|
@@ -968,9 +1031,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
968
1031
|
self.__setup_queue = None
|
969
1032
|
|
970
1033
|
def __setup(self) -> None:
|
971
|
-
queue
|
972
|
-
|
973
|
-
if queue is None:
|
1034
|
+
if (queue := self.__setup_queue) is None:
|
974
1035
|
return
|
975
1036
|
|
976
1037
|
while True:
|
@@ -1037,11 +1098,13 @@ class SyncInjectedFunction[**P, T](InjectedFunction[P, T]):
|
|
1037
1098
|
return self.__inject_metadata__.call(*args, **kwargs)
|
1038
1099
|
|
1039
1100
|
|
1040
|
-
def
|
1101
|
+
def extract_caller[**P, T](
|
1102
|
+
function: Callable[P, T] | Callable[P, Awaitable[T]],
|
1103
|
+
) -> Caller[P, T]:
|
1041
1104
|
if iscoroutinefunction(function):
|
1042
1105
|
return AsyncCaller(function)
|
1043
1106
|
|
1044
1107
|
elif isinstance(function, InjectedFunction):
|
1045
1108
|
return function.__inject_metadata__
|
1046
1109
|
|
1047
|
-
return SyncCaller(function)
|
1110
|
+
return SyncCaller(function) # type: ignore[arg-type]
|
injection/_core/scope.py
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from collections import defaultdict
|
5
|
+
from collections.abc import AsyncIterator, Iterator, MutableMapping
|
6
|
+
from contextlib import (
|
7
|
+
AsyncContextDecorator,
|
8
|
+
AsyncExitStack,
|
9
|
+
ContextDecorator,
|
10
|
+
ExitStack,
|
11
|
+
asynccontextmanager,
|
12
|
+
contextmanager,
|
13
|
+
)
|
14
|
+
from contextvars import ContextVar
|
15
|
+
from dataclasses import dataclass, field
|
16
|
+
from types import TracebackType
|
17
|
+
from typing import (
|
18
|
+
Any,
|
19
|
+
AsyncContextManager,
|
20
|
+
ContextManager,
|
21
|
+
Final,
|
22
|
+
Protocol,
|
23
|
+
Self,
|
24
|
+
runtime_checkable,
|
25
|
+
)
|
26
|
+
|
27
|
+
from injection._core.common.key import new_short_key
|
28
|
+
from injection.exceptions import (
|
29
|
+
ScopeAlreadyDefinedError,
|
30
|
+
ScopeError,
|
31
|
+
ScopeUndefinedError,
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
@dataclass(repr=False, slots=True)
|
36
|
+
class _ScopeState:
|
37
|
+
# Shouldn't be instantiated outside `__SCOPES`.
|
38
|
+
|
39
|
+
__context_var: ContextVar[Scope] = field(
|
40
|
+
default_factory=lambda: ContextVar(f"scope@{new_short_key()}"),
|
41
|
+
init=False,
|
42
|
+
)
|
43
|
+
__references: set[Scope] = field(
|
44
|
+
default_factory=set,
|
45
|
+
init=False,
|
46
|
+
)
|
47
|
+
__shared_value: Scope | None = field(
|
48
|
+
default=None,
|
49
|
+
init=False,
|
50
|
+
)
|
51
|
+
|
52
|
+
@property
|
53
|
+
def active_scopes(self) -> Iterator[Scope]:
|
54
|
+
yield from self.__references
|
55
|
+
|
56
|
+
if shared_value := self.__shared_value:
|
57
|
+
yield shared_value
|
58
|
+
|
59
|
+
@contextmanager
|
60
|
+
def bind_contextual_scope(self, scope: Scope) -> Iterator[None]:
|
61
|
+
self.__references.add(scope)
|
62
|
+
token = self.__context_var.set(scope)
|
63
|
+
|
64
|
+
try:
|
65
|
+
yield
|
66
|
+
finally:
|
67
|
+
self.__context_var.reset(token)
|
68
|
+
self.__references.remove(scope)
|
69
|
+
|
70
|
+
@contextmanager
|
71
|
+
def bind_shared_scope(self, scope: Scope) -> Iterator[None]:
|
72
|
+
if next(self.active_scopes, None):
|
73
|
+
raise ScopeError(
|
74
|
+
"A shared scope can't be defined when one or more contextual scopes "
|
75
|
+
"are defined on the same name."
|
76
|
+
)
|
77
|
+
|
78
|
+
self.__shared_value = scope
|
79
|
+
|
80
|
+
try:
|
81
|
+
yield
|
82
|
+
finally:
|
83
|
+
self.__shared_value = None
|
84
|
+
|
85
|
+
def get_scope(self) -> Scope | None:
|
86
|
+
return self.__context_var.get(self.__shared_value)
|
87
|
+
|
88
|
+
|
89
|
+
__SCOPES: Final[defaultdict[str, _ScopeState]] = defaultdict(_ScopeState)
|
90
|
+
|
91
|
+
|
92
|
+
@asynccontextmanager
|
93
|
+
async def adefine_scope(name: str, *, shared: bool = False) -> AsyncIterator[None]:
|
94
|
+
async with AsyncScope() as scope:
|
95
|
+
scope.enter(_bind_scope(name, scope, shared))
|
96
|
+
yield
|
97
|
+
|
98
|
+
|
99
|
+
@contextmanager
|
100
|
+
def define_scope(name: str, *, shared: bool = False) -> Iterator[None]:
|
101
|
+
with SyncScope() as scope:
|
102
|
+
scope.enter(_bind_scope(name, scope, shared))
|
103
|
+
yield
|
104
|
+
|
105
|
+
|
106
|
+
def get_active_scopes(name: str) -> tuple[Scope, ...]:
|
107
|
+
return tuple(__SCOPES[name].active_scopes)
|
108
|
+
|
109
|
+
|
110
|
+
def get_scope(name: str) -> Scope:
|
111
|
+
scope = __SCOPES[name].get_scope()
|
112
|
+
|
113
|
+
if scope is None:
|
114
|
+
raise ScopeUndefinedError(
|
115
|
+
f"Scope `{name}` isn't defined in the current context."
|
116
|
+
)
|
117
|
+
|
118
|
+
return scope
|
119
|
+
|
120
|
+
|
121
|
+
@contextmanager
|
122
|
+
def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
|
123
|
+
state = __SCOPES[name]
|
124
|
+
|
125
|
+
if state.get_scope():
|
126
|
+
raise ScopeAlreadyDefinedError(
|
127
|
+
f"Scope `{name}` is already defined in the current context."
|
128
|
+
)
|
129
|
+
|
130
|
+
strategy = (
|
131
|
+
state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
|
132
|
+
)
|
133
|
+
|
134
|
+
try:
|
135
|
+
with strategy:
|
136
|
+
yield
|
137
|
+
finally:
|
138
|
+
scope.cache.clear()
|
139
|
+
|
140
|
+
|
141
|
+
@runtime_checkable
|
142
|
+
class Scope(Protocol):
|
143
|
+
__slots__ = ()
|
144
|
+
|
145
|
+
cache: MutableMapping[Any, Any]
|
146
|
+
|
147
|
+
@abstractmethod
|
148
|
+
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
|
149
|
+
raise NotImplementedError
|
150
|
+
|
151
|
+
@abstractmethod
|
152
|
+
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
153
|
+
raise NotImplementedError
|
154
|
+
|
155
|
+
|
156
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
157
|
+
class BaseScope[T](Scope, ABC):
|
158
|
+
delegate: T
|
159
|
+
cache: MutableMapping[Any, Any] = field(
|
160
|
+
default_factory=dict,
|
161
|
+
init=False,
|
162
|
+
hash=False,
|
163
|
+
)
|
164
|
+
|
165
|
+
|
166
|
+
class AsyncScope(AsyncContextDecorator, BaseScope[AsyncExitStack]):
|
167
|
+
__slots__ = ()
|
168
|
+
|
169
|
+
def __init__(self) -> None:
|
170
|
+
super().__init__(delegate=AsyncExitStack())
|
171
|
+
|
172
|
+
async def __aenter__(self) -> Self:
|
173
|
+
await self.delegate.__aenter__()
|
174
|
+
return self
|
175
|
+
|
176
|
+
async def __aexit__(
|
177
|
+
self,
|
178
|
+
exc_type: type[BaseException] | None,
|
179
|
+
exc_value: BaseException | None,
|
180
|
+
traceback: TracebackType | None,
|
181
|
+
) -> Any:
|
182
|
+
return await self.delegate.__aexit__(exc_type, exc_value, traceback)
|
183
|
+
|
184
|
+
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
|
185
|
+
return await self.delegate.enter_async_context(context_manager)
|
186
|
+
|
187
|
+
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
188
|
+
return self.delegate.enter_context(context_manager)
|
189
|
+
|
190
|
+
|
191
|
+
class SyncScope(ContextDecorator, BaseScope[ExitStack]):
|
192
|
+
__slots__ = ()
|
193
|
+
|
194
|
+
def __init__(self) -> None:
|
195
|
+
super().__init__(delegate=ExitStack())
|
196
|
+
|
197
|
+
def __enter__(self) -> Self:
|
198
|
+
self.delegate.__enter__()
|
199
|
+
return self
|
200
|
+
|
201
|
+
def __exit__(
|
202
|
+
self,
|
203
|
+
exc_type: type[BaseException] | None,
|
204
|
+
exc_value: BaseException | None,
|
205
|
+
traceback: TracebackType | None,
|
206
|
+
) -> Any:
|
207
|
+
return self.delegate.__exit__(exc_type, exc_value, traceback)
|
208
|
+
|
209
|
+
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
|
210
|
+
raise ScopeError(
|
211
|
+
"Synchronous scope doesn't support asynchronous context manager."
|
212
|
+
)
|
213
|
+
|
214
|
+
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
215
|
+
return self.delegate.enter_context(context_manager)
|
injection/exceptions.py
CHANGED
@@ -7,6 +7,9 @@ __all__ = (
|
|
7
7
|
"ModuleLockError",
|
8
8
|
"ModuleNotUsedError",
|
9
9
|
"NoInjectable",
|
10
|
+
"ScopeAlreadyDefinedError",
|
11
|
+
"ScopeError",
|
12
|
+
"ScopeUndefinedError",
|
10
13
|
)
|
11
14
|
|
12
15
|
|
@@ -36,4 +39,13 @@ class ModuleLockError(ModuleError): ...
|
|
36
39
|
class ModuleNotUsedError(KeyError, ModuleError): ...
|
37
40
|
|
38
41
|
|
42
|
+
class ScopeError(InjectionError): ...
|
43
|
+
|
44
|
+
|
45
|
+
class ScopeUndefinedError(LookupError, ScopeError): ...
|
46
|
+
|
47
|
+
|
48
|
+
class ScopeAlreadyDefinedError(ScopeError): ...
|
49
|
+
|
50
|
+
|
39
51
|
class HookError(InjectionError): ...
|
@@ -5,7 +5,6 @@ from typing import Any, TypeAliasType
|
|
5
5
|
from fastapi import Depends
|
6
6
|
|
7
7
|
from injection import Module, mod
|
8
|
-
from injection.exceptions import InjectionError
|
9
8
|
|
10
9
|
__all__ = ("Inject",)
|
11
10
|
|
@@ -14,50 +13,26 @@ def Inject[T]( # noqa: N802
|
|
14
13
|
cls: type[T] | TypeAliasType | GenericAlias,
|
15
14
|
/,
|
16
15
|
module: Module | None = None,
|
17
|
-
*,
|
18
|
-
scoped: bool = True,
|
19
16
|
) -> Any:
|
20
17
|
"""
|
21
18
|
Declare a FastAPI dependency with `python-injection`.
|
22
19
|
"""
|
23
20
|
|
24
21
|
dependency: InjectionDependency[T] = InjectionDependency(cls, module or mod())
|
25
|
-
return Depends(dependency, use_cache=
|
22
|
+
return Depends(dependency, use_cache=False)
|
26
23
|
|
27
24
|
|
28
25
|
class InjectionDependency[T]:
|
29
|
-
__slots__ = ("
|
26
|
+
__slots__ = ("__awaitable",)
|
30
27
|
|
31
|
-
|
32
|
-
__lazy_instance: Awaitable[T]
|
33
|
-
__module: Module
|
28
|
+
__awaitable: Awaitable[T]
|
34
29
|
|
35
30
|
def __init__(
|
36
31
|
self,
|
37
32
|
cls: type[T] | TypeAliasType | GenericAlias,
|
38
33
|
module: Module,
|
39
34
|
) -> None:
|
40
|
-
self.
|
41
|
-
self.__lazy_instance = module.aget_lazy_instance(cls, default=NotImplemented)
|
42
|
-
self.__module = module
|
35
|
+
self.__awaitable = module.aget_lazy_instance(cls, default=NotImplemented)
|
43
36
|
|
44
37
|
async def __call__(self) -> T:
|
45
|
-
|
46
|
-
|
47
|
-
if instance is NotImplemented:
|
48
|
-
raise InjectionError(f"`{self.__class}` is an unknown dependency.")
|
49
|
-
|
50
|
-
return instance
|
51
|
-
|
52
|
-
def __eq__(self, other: Any) -> bool:
|
53
|
-
if isinstance(other, type(self)):
|
54
|
-
return self.__key == other.__key
|
55
|
-
|
56
|
-
return NotImplemented
|
57
|
-
|
58
|
-
def __hash__(self) -> int:
|
59
|
-
return hash(self.__key)
|
60
|
-
|
61
|
-
@property
|
62
|
-
def __key(self) -> tuple[type[T] | TypeAliasType | GenericAlias, Module]:
|
63
|
-
return self.__class, self.__module
|
38
|
+
return await self.__awaitable
|
injection/testing/__init__.py
CHANGED
@@ -9,6 +9,7 @@ __all__ = (
|
|
9
9
|
"should_be_test_injectable",
|
10
10
|
"test_constant",
|
11
11
|
"test_injectable",
|
12
|
+
"test_scoped",
|
12
13
|
"test_singleton",
|
13
14
|
)
|
14
15
|
|
@@ -18,6 +19,7 @@ set_test_constant = mod(_TEST_PROFILE_NAME).set_constant
|
|
18
19
|
should_be_test_injectable = mod(_TEST_PROFILE_NAME).should_be_injectable
|
19
20
|
test_constant = mod(_TEST_PROFILE_NAME).constant
|
20
21
|
test_injectable = mod(_TEST_PROFILE_NAME).injectable
|
22
|
+
test_scoped = mod(_TEST_PROFILE_NAME).scoped
|
21
23
|
test_singleton = mod(_TEST_PROFILE_NAME).singleton
|
22
24
|
|
23
25
|
|
injection/testing/__init__.pyi
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
from typing import ContextManager
|
1
|
+
from typing import ContextManager, Final
|
2
2
|
|
3
|
-
|
3
|
+
from injection import Module
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
__MODULE: Final[Module] = ...
|
6
|
+
|
7
|
+
set_test_constant = __MODULE.set_constant
|
8
|
+
should_be_test_injectable = __MODULE.should_be_injectable
|
9
|
+
test_constant = __MODULE.constant
|
10
|
+
test_injectable = __MODULE.injectable
|
11
|
+
test_scoped = __MODULE.scoped
|
12
|
+
test_singleton = __MODULE.singleton
|
10
13
|
|
11
14
|
def load_test_profile(*names: str) -> ContextManager[None]:
|
12
15
|
"""
|
injection/utils.py
CHANGED
@@ -42,12 +42,17 @@ def load_modules_with_keywords(
|
|
42
42
|
"""
|
43
43
|
Function to import modules from a Python package if one of the keywords is contained in the Python script.
|
44
44
|
The default keywords are:
|
45
|
-
- `from injection`
|
45
|
+
- `from injection `
|
46
|
+
- `from injection.`
|
46
47
|
- `import injection`
|
47
48
|
"""
|
48
49
|
|
49
50
|
if keywords is None:
|
50
|
-
keywords =
|
51
|
+
keywords = (
|
52
|
+
f"from {injection_package_name} ",
|
53
|
+
f"from {injection_package_name}.",
|
54
|
+
f"import {injection_package_name}",
|
55
|
+
)
|
51
56
|
|
52
57
|
b_keywords = tuple(keyword.encode() for keyword in keywords)
|
53
58
|
|
@@ -1,31 +1,30 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: python-injection
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.0
|
4
4
|
Summary: Fast and easy dependency injection framework.
|
5
|
-
|
5
|
+
Project-URL: Repository, https://github.com/100nm/python-injection
|
6
|
+
Author: remimd
|
6
7
|
License: MIT
|
7
8
|
Keywords: dependencies,dependency,inject,injection
|
8
|
-
Author: remimd
|
9
|
-
Requires-Python: >=3.12, <4
|
10
9
|
Classifier: Development Status :: 4 - Beta
|
10
|
+
Classifier: Intended Audience :: Developers
|
11
|
+
Classifier: Natural Language :: English
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Programming Language :: Python
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
11
16
|
Classifier: Topic :: Software Development :: Libraries
|
12
17
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
13
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
14
19
|
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
|
21
20
|
Classifier: Typing :: Typed
|
22
|
-
|
21
|
+
Requires-Python: <4,>=3.12
|
23
22
|
Description-Content-Type: text/markdown
|
24
23
|
|
25
24
|
# python-injection
|
26
25
|
|
27
26
|
[](https://github.com/100nm/python-injection)
|
28
|
-
[](https://pypi.org/project/python-injection
|
27
|
+
[](https://pypi.org/project/python-injection)
|
29
28
|
[](https://github.com/astral-sh/ruff)
|
30
29
|
|
31
30
|
Fast and easy dependency injection framework.
|
@@ -80,9 +79,9 @@ if __name__ == "__main__":
|
|
80
79
|
## Resources
|
81
80
|
|
82
81
|
* [**Basic usage**](https://github.com/100nm/python-injection/tree/prod/documentation/basic-usage.md)
|
82
|
+
* [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
|
83
83
|
* [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
|
84
84
|
* [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
|
85
85
|
* [**Utils**](https://github.com/100nm/python-injection/tree/prod/documentation/utils.md)
|
86
86
|
* [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
|
87
|
-
* [**Concrete example**](https://github.com/100nm/python-injection
|
88
|
-
|
87
|
+
* [**Concrete example**](https://github.com/100nm/python-injection-example)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
injection/__init__.py,sha256=X0vIAoN4MDlhR7YIkup1qHgqbOkwZ3PWgdSi7h-udaM,1049
|
2
|
+
injection/__init__.pyi,sha256=XlDjgy-gLz9LKBER0478b_9UMatwdQ4p0zKpaPAaF9M,9572
|
3
|
+
injection/exceptions.py,sha256=T__732aXxWWUz0sKc39ySteyolCS5tpqQC0oCnzUF2E,917
|
4
|
+
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
injection/utils.py,sha256=SZBzmVAwRL-kilS2eRt9MCeNAL4zeaj_7Rm_2NLBzg0,3175
|
6
|
+
injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
|
7
|
+
injection/_core/descriptors.py,sha256=y1rFTQdCDIMLVQfuQE8ZkTPlVZKgzvwZ2Y20Si05DwM,662
|
8
|
+
injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
|
9
|
+
injection/_core/injectables.py,sha256=SApYnP6vG3b1qi_KJx6hkJyPoyTmZfooxqPpOsxWSVI,4482
|
10
|
+
injection/_core/module.py,sha256=MvXxy5Yrb3tOTgD5qCWwZVgMBzHGVlqtcceYA51dLNk,30550
|
11
|
+
injection/_core/scope.py,sha256=e6SYiiWZuQluGWxEAT2T-2M6Y67r-89FgGG0rM4HP-s,5673
|
12
|
+
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
injection/_core/common/asynchronous.py,sha256=LlYMde_55osS3r8Sc3Fh5lgPp5UWmIfvyBzUeIDmMiM,1698
|
14
|
+
injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
|
15
|
+
injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
|
16
|
+
injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
|
17
|
+
injection/_core/common/lazy.py,sha256=ZKx2O9CCFsF9F0SLM4zb7jSLksJUv-FBLCPlWltMN5k,1398
|
18
|
+
injection/_core/common/type.py,sha256=QbBBhJp7i1p6gLzWX0TgofvfG7yDH-gHfEQcssVZeHo,2186
|
19
|
+
injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
injection/integrations/fastapi.py,sha256=EYjaQ2RpdPFMj6CDQWg3YYk9Y9wujbdZkm63VRhpTt4,908
|
21
|
+
injection/testing/__init__.py,sha256=ALcKuDYNdslmpgqotZzSWrXAW0kNNFUs9nzfO1ZgIGc,783
|
22
|
+
injection/testing/__init__.pyi,sha256=6rv5NOYHEaiKMd82E1IIc8lFlLV9ttY57DLiMqGTXt8,482
|
23
|
+
python_injection-0.13.0.dist-info/METADATA,sha256=P_YxmXOtCUAkg8V0pnjikcl9B702McvBakiBjv6AGik,2996
|
24
|
+
python_injection-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
python_injection-0.13.0.dist-info/RECORD,,
|
@@ -1,24 +0,0 @@
|
|
1
|
-
injection/__init__.py,sha256=1MI9kXuQfsqLGQK_5-Zm4JjecqopjGNlhsAW8iihn9M,919
|
2
|
-
injection/__init__.pyi,sha256=Gwtvo6sJVizqz0pTKNaG-Jz_NW0gSqEsoZF7T6p3V1s,8820
|
3
|
-
injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
|
4
|
-
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
injection/_core/common/asynchronous.py,sha256=Sd7r5y42WSNXRtC4NvYRn5y2Jb6nNapj0aAOvkS4WIU,1726
|
6
|
-
injection/_core/common/event.py,sha256=kev8WUESAXzMn11EJsbTcICaCvYiD556E6BOLvQ54s4,1243
|
7
|
-
injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
|
8
|
-
injection/_core/common/lazy.py,sha256=ZKx2O9CCFsF9F0SLM4zb7jSLksJUv-FBLCPlWltMN5k,1398
|
9
|
-
injection/_core/common/threading.py,sha256=OXm7L3p8c7O7eSkU-RTR7cobqIGMhuo-7gpDXsWKDNQ,214
|
10
|
-
injection/_core/common/type.py,sha256=c4QfvbkMfYMlNxqt-vq6QJ83ubMnw6AIVI2Rp-tV1PI,1550
|
11
|
-
injection/_core/descriptors.py,sha256=y1rFTQdCDIMLVQfuQE8ZkTPlVZKgzvwZ2Y20Si05DwM,662
|
12
|
-
injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
|
13
|
-
injection/_core/injectables.py,sha256=rBcrTYRpZ69LkHSGm6bve6TGPwf66XkuK2XVACwcRNc,2427
|
14
|
-
injection/_core/module.py,sha256=e0FVvhplVRbOGrAT-RCNKIR2pTng5qEAxCjuurm0cEA,28675
|
15
|
-
injection/exceptions.py,sha256=-5Shs7R5rctQXhpMLfcjiMBCzrtFWxC88qETUIHz57s,692
|
16
|
-
injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
injection/integrations/fastapi.py,sha256=tI9ohXOm_WucqkS-DZJceIVb_mE9LOY_UpIcr6hfRVM,1668
|
18
|
-
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
injection/testing/__init__.py,sha256=Wd9sq43CJCV7GjYFSIaikIf4hJ5lfVNS7GP3iI8a1X8,719
|
20
|
-
injection/testing/__init__.pyi,sha256=Dyf1LjSj3LrjghXjM2xkMiINg-OPWQMtjCEnnfWzaDY,372
|
21
|
-
injection/utils.py,sha256=a9y95B08Fr9IgDiBffeQdcawQcvAs5gwBDt9cCYX0uE,3061
|
22
|
-
python_injection-0.12.3.dist-info/METADATA,sha256=8AyuGaX3P5JfCRxQBcnMrwkssXYeKP52UC64xJ5XE-o,2958
|
23
|
-
python_injection-0.12.3.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
|
24
|
-
python_injection-0.12.3.dist-info/RECORD,,
|