python-injection 0.14.5__py3-none-any.whl → 0.14.6.post0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- injection/_core/common/asynchronous.py +13 -1
- injection/_core/injectables.py +70 -52
- injection/_core/module.py +2 -1
- injection/_core/scope.py +8 -3
- injection/_core/slots.py +3 -3
- {python_injection-0.14.5.dist-info → python_injection-0.14.6.post0.dist-info}/METADATA +1 -1
- {python_injection-0.14.5.dist-info → python_injection-0.14.6.post0.dist-info}/RECORD +8 -8
- {python_injection-0.14.5.dist-info → python_injection-0.14.6.post0.dist-info}/WHEEL +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
from abc import abstractmethod
|
2
2
|
from collections.abc import Awaitable, Callable, Generator
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import Any, NoReturn, Protocol, runtime_checkable
|
4
|
+
from typing import Any, AsyncContextManager, NoReturn, Protocol, runtime_checkable
|
5
5
|
|
6
6
|
|
7
7
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
@@ -45,3 +45,15 @@ class SyncCaller[**P, T](Caller[P, T]):
|
|
45
45
|
|
46
46
|
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
47
47
|
return self.callable(*args, **kwargs)
|
48
|
+
|
49
|
+
|
50
|
+
try:
|
51
|
+
import anyio
|
52
|
+
|
53
|
+
def create_semaphore(value: int) -> AsyncContextManager[Any]:
|
54
|
+
return anyio.Semaphore(value)
|
55
|
+
except ImportError: # pragma: no cover
|
56
|
+
import asyncio
|
57
|
+
|
58
|
+
def create_semaphore(value: int) -> AsyncContextManager[Any]:
|
59
|
+
return asyncio.Semaphore(value)
|
injection/_core/injectables.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from collections.abc import MutableMapping
|
2
|
+
from collections.abc import Awaitable, Callable, MutableMapping
|
3
3
|
from contextlib import suppress
|
4
|
-
from dataclasses import dataclass
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from functools import partial
|
5
6
|
from typing import (
|
6
7
|
Any,
|
7
8
|
AsyncContextManager,
|
@@ -13,6 +14,9 @@ from typing import (
|
|
13
14
|
)
|
14
15
|
|
15
16
|
from injection._core.common.asynchronous import Caller
|
17
|
+
from injection._core.common.asynchronous import (
|
18
|
+
create_semaphore as _create_async_semaphore,
|
19
|
+
)
|
16
20
|
from injection._core.scope import Scope, get_active_scopes, get_scope
|
17
21
|
from injection.exceptions import InjectionError
|
18
22
|
|
@@ -37,14 +41,10 @@ class Injectable[T](Protocol):
|
|
37
41
|
raise NotImplementedError
|
38
42
|
|
39
43
|
|
40
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
41
|
-
class
|
44
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
45
|
+
class SimpleInjectable[T](Injectable[T]):
|
42
46
|
factory: Caller[..., T]
|
43
47
|
|
44
|
-
|
45
|
-
class SimpleInjectable[T](BaseInjectable[T]):
|
46
|
-
__slots__ = ()
|
47
|
-
|
48
48
|
async def aget_instance(self) -> T:
|
49
49
|
return await self.factory.acall()
|
50
50
|
|
@@ -52,47 +52,74 @@ class SimpleInjectable[T](BaseInjectable[T]):
|
|
52
52
|
return self.factory.call()
|
53
53
|
|
54
54
|
|
55
|
-
class
|
56
|
-
__slots__ = ("
|
55
|
+
class CacheLogic[T]:
|
56
|
+
__slots__ = ("__semaphore",)
|
57
57
|
|
58
|
-
|
58
|
+
__semaphore: AsyncContextManager[Any]
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
return self.__key in self.__cache
|
60
|
+
def __init__(self) -> None:
|
61
|
+
self.__semaphore = _create_async_semaphore(1)
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
async def aget_or_create[K](
|
64
|
+
self,
|
65
|
+
cache: MutableMapping[K, T],
|
66
|
+
key: K,
|
67
|
+
factory: Callable[..., Awaitable[T]],
|
68
|
+
) -> T:
|
69
|
+
async with self.__semaphore:
|
70
|
+
with suppress(KeyError):
|
71
|
+
return cache[key]
|
67
72
|
|
68
|
-
|
69
|
-
|
73
|
+
instance = await factory()
|
74
|
+
cache[key] = instance
|
70
75
|
|
76
|
+
return instance
|
77
|
+
|
78
|
+
def get_or_create[K](
|
79
|
+
self,
|
80
|
+
cache: MutableMapping[K, T],
|
81
|
+
key: K,
|
82
|
+
factory: Callable[..., T],
|
83
|
+
) -> T:
|
71
84
|
with suppress(KeyError):
|
72
|
-
return cache[
|
85
|
+
return cache[key]
|
73
86
|
|
74
|
-
instance =
|
75
|
-
cache[
|
87
|
+
instance = factory()
|
88
|
+
cache[key] = instance
|
76
89
|
return instance
|
77
90
|
|
78
|
-
def get_instance(self) -> T:
|
79
|
-
cache = self.__cache
|
80
91
|
|
81
|
-
|
82
|
-
|
92
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
93
|
+
class SingletonInjectable[T](Injectable[T]):
|
94
|
+
factory: Caller[..., T]
|
95
|
+
cache: MutableMapping[str, T] = field(default_factory=dict)
|
96
|
+
logic: CacheLogic[T] = field(default_factory=CacheLogic)
|
83
97
|
|
84
|
-
|
85
|
-
|
86
|
-
|
98
|
+
__key: ClassVar[str] = "$instance"
|
99
|
+
|
100
|
+
@property
|
101
|
+
def is_locked(self) -> bool:
|
102
|
+
return self.__key in self.cache
|
103
|
+
|
104
|
+
async def aget_instance(self) -> T:
|
105
|
+
return await self.logic.aget_or_create(
|
106
|
+
self.cache,
|
107
|
+
self.__key,
|
108
|
+
self.factory.acall,
|
109
|
+
)
|
110
|
+
|
111
|
+
def get_instance(self) -> T:
|
112
|
+
return self.logic.get_or_create(self.cache, self.__key, self.factory.call)
|
87
113
|
|
88
114
|
def unlock(self) -> None:
|
89
|
-
self.
|
115
|
+
self.cache.pop(self.__key, None)
|
90
116
|
|
91
117
|
|
92
118
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
93
119
|
class ScopedInjectable[R, T](Injectable[T], ABC):
|
94
120
|
factory: Caller[..., R]
|
95
121
|
scope_name: str
|
122
|
+
logic: CacheLogic[T] = field(default_factory=CacheLogic)
|
96
123
|
|
97
124
|
@property
|
98
125
|
def is_locked(self) -> bool:
|
@@ -107,35 +134,26 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
107
134
|
raise NotImplementedError
|
108
135
|
|
109
136
|
async def aget_instance(self) -> T:
|
110
|
-
scope = self.
|
111
|
-
|
112
|
-
|
113
|
-
return scope.cache[self]
|
114
|
-
|
115
|
-
instance = await self.abuild(scope)
|
116
|
-
self.set_instance(instance, scope)
|
117
|
-
return instance
|
137
|
+
scope = self.__get_scope()
|
138
|
+
factory = partial(self.abuild, scope)
|
139
|
+
return await self.logic.aget_or_create(scope.cache, self, factory)
|
118
140
|
|
119
141
|
def get_instance(self) -> T:
|
120
|
-
scope = self.
|
142
|
+
scope = self.__get_scope()
|
143
|
+
factory = partial(self.build, scope)
|
144
|
+
return self.logic.get_or_create(scope.cache, self, factory)
|
121
145
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
instance = self.build(scope)
|
126
|
-
self.set_instance(instance, scope)
|
127
|
-
return instance
|
128
|
-
|
129
|
-
def get_scope(self) -> Scope:
|
130
|
-
return get_scope(self.scope_name)
|
131
|
-
|
132
|
-
def set_instance(self, instance: T, scope: Scope) -> None:
|
133
|
-
scope.cache[self] = instance
|
146
|
+
def setdefault(self, instance: T) -> T:
|
147
|
+
scope = self.__get_scope()
|
148
|
+
return self.logic.get_or_create(scope.cache, self, lambda: instance)
|
134
149
|
|
135
150
|
def unlock(self) -> None:
|
136
151
|
if self.is_locked:
|
137
152
|
raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
|
138
153
|
|
154
|
+
def __get_scope(self) -> Scope:
|
155
|
+
return get_scope(self.scope_name)
|
156
|
+
|
139
157
|
|
140
158
|
class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
|
141
159
|
__slots__ = ()
|
@@ -174,7 +192,7 @@ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
|
174
192
|
scope.cache.pop(self, None)
|
175
193
|
|
176
194
|
|
177
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
195
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
178
196
|
class ShouldBeInjectable[T](Injectable[T]):
|
179
197
|
cls: type[T]
|
180
198
|
|
injection/_core/module.py
CHANGED
@@ -300,7 +300,8 @@ class Locator(Broker):
|
|
300
300
|
if injectable.is_locked:
|
301
301
|
continue
|
302
302
|
|
303
|
-
|
303
|
+
with suppress(SkipInjectable):
|
304
|
+
await injectable.aget_instance()
|
304
305
|
|
305
306
|
def add_listener(self, listener: EventListener) -> Self:
|
306
307
|
self.__channel.add_listener(listener)
|
injection/_core/scope.py
CHANGED
@@ -98,13 +98,18 @@ def define_scope(name: str, *, shared: bool = False) -> Iterator[None]:
|
|
98
98
|
|
99
99
|
|
100
100
|
def get_active_scopes(name: str) -> tuple[Scope, ...]:
|
101
|
-
|
101
|
+
state = __SCOPES.get(name)
|
102
|
+
|
103
|
+
if state is None:
|
104
|
+
return ()
|
105
|
+
|
106
|
+
return tuple(state.active_scopes)
|
102
107
|
|
103
108
|
|
104
109
|
def get_scope(name: str) -> Scope:
|
105
|
-
|
110
|
+
state = __SCOPES.get(name)
|
106
111
|
|
107
|
-
if scope is None:
|
112
|
+
if state is None or (scope := state.get_scope()) is None:
|
108
113
|
raise ScopeUndefinedError(
|
109
114
|
f"Scope `{name}` isn't defined in the current context."
|
110
115
|
)
|
injection/_core/slots.py
CHANGED
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
3
3
|
from typing import Any, Protocol, runtime_checkable
|
4
4
|
|
5
5
|
from injection._core.injectables import ScopedInjectable
|
6
|
+
from injection.exceptions import InjectionError
|
6
7
|
|
7
8
|
|
8
9
|
@runtime_checkable
|
@@ -19,6 +20,5 @@ class ScopedSlot[T](Slot[T]):
|
|
19
20
|
injectable: ScopedInjectable[Any, T]
|
20
21
|
|
21
22
|
def set(self, instance: T, /) -> None:
|
22
|
-
|
23
|
-
|
24
|
-
injectable.set_instance(instance, scope)
|
23
|
+
if self.injectable.setdefault(instance) is not instance:
|
24
|
+
raise InjectionError("Slot already set.")
|
@@ -5,12 +5,12 @@ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
injection/utils.py,sha256=EuHMrix6gx2YnnUAn2_BPsDkvucGS5-pFhM3596oBK4,2796
|
6
6
|
injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
|
8
|
-
injection/_core/injectables.py,sha256=
|
9
|
-
injection/_core/module.py,sha256=
|
10
|
-
injection/_core/scope.py,sha256=
|
11
|
-
injection/_core/slots.py,sha256=
|
8
|
+
injection/_core/injectables.py,sha256=idNkQZZ29vd73G_lE-eS5C7zGeVe_ALNkUt8M6YjZrk,5519
|
9
|
+
injection/_core/module.py,sha256=DLw0pD3HDXfNhzbWM0yeCDKg-Mwg8JAzqZq43tFAXik,31814
|
10
|
+
injection/_core/scope.py,sha256=SnjfYnZ62BkxEUh3wXKHl7ivCHRrPFiTa5GMxC-8ACM,5533
|
11
|
+
injection/_core/slots.py,sha256=6LoG0XtaRnIGDSG8s-FfUIw_50gL0bl4X3Fo_n-hdak,680
|
12
12
|
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
injection/_core/common/asynchronous.py,sha256=
|
13
|
+
injection/_core/common/asynchronous.py,sha256=QeS2Lc4gEBFvTA_snOWfme5mTL4BFZWqZ8EzJwOdVos,1816
|
14
14
|
injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
|
15
15
|
injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
|
16
16
|
injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
|
@@ -20,6 +20,6 @@ injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
20
20
|
injection/integrations/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
|
21
21
|
injection/testing/__init__.py,sha256=SiImXDd0-DO1a8S5nbUQRtgDX8iaU_nHcp8DdqwtD2M,896
|
22
22
|
injection/testing/__init__.pyi,sha256=iOii0i9F5n7znltGeGQYI2KXC_if9SAogLh1h03yx-0,540
|
23
|
-
python_injection-0.14.
|
24
|
-
python_injection-0.14.
|
25
|
-
python_injection-0.14.
|
23
|
+
python_injection-0.14.6.post0.dist-info/METADATA,sha256=sRxRmH0mUX84dvnm-e2Vw6dyZrSOqNqaoVdn5A82U_4,3205
|
24
|
+
python_injection-0.14.6.post0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
python_injection-0.14.6.post0.dist-info/RECORD,,
|
File without changes
|