python-injection 0.14.5__py3-none-any.whl → 0.14.6__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 +57 -43
- 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.dist-info}/METADATA +1 -1
- {python_injection-0.14.5.dist-info → python_injection-0.14.6.dist-info}/RECORD +8 -8
- {python_injection-0.14.5.dist-info → python_injection-0.14.6.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,
|
@@ -12,7 +13,7 @@ from typing import (
|
|
12
13
|
runtime_checkable,
|
13
14
|
)
|
14
15
|
|
15
|
-
from injection._core.common.asynchronous import Caller
|
16
|
+
from injection._core.common.asynchronous import Caller, create_semaphore
|
16
17
|
from injection._core.scope import Scope, get_active_scopes, get_scope
|
17
18
|
from injection.exceptions import InjectionError
|
18
19
|
|
@@ -37,12 +38,12 @@ class Injectable[T](Protocol):
|
|
37
38
|
raise NotImplementedError
|
38
39
|
|
39
40
|
|
40
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
41
|
-
class BaseInjectable[T](Injectable[T], ABC):
|
42
|
-
factory: Caller[...,
|
41
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
42
|
+
class BaseInjectable[R, T](Injectable[T], ABC):
|
43
|
+
factory: Caller[..., R]
|
43
44
|
|
44
45
|
|
45
|
-
class SimpleInjectable[T](BaseInjectable[T]):
|
46
|
+
class SimpleInjectable[T](BaseInjectable[T, T]):
|
46
47
|
__slots__ = ()
|
47
48
|
|
48
49
|
async def aget_instance(self) -> T:
|
@@ -52,7 +53,44 @@ class SimpleInjectable[T](BaseInjectable[T]):
|
|
52
53
|
return self.factory.call()
|
53
54
|
|
54
55
|
|
55
|
-
|
56
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
57
|
+
class CachedInjectable[R, T](BaseInjectable[R, T], ABC):
|
58
|
+
__semaphore: AsyncContextManager[Any] = field(
|
59
|
+
default_factory=partial(create_semaphore, 1),
|
60
|
+
init=False,
|
61
|
+
hash=False,
|
62
|
+
)
|
63
|
+
|
64
|
+
async def aget_or_create[K](
|
65
|
+
self,
|
66
|
+
cache: MutableMapping[K, T],
|
67
|
+
key: K,
|
68
|
+
factory: Callable[..., Awaitable[T]],
|
69
|
+
) -> T:
|
70
|
+
async with self.__semaphore:
|
71
|
+
with suppress(KeyError):
|
72
|
+
return cache[key]
|
73
|
+
|
74
|
+
instance = await factory()
|
75
|
+
cache[key] = instance
|
76
|
+
|
77
|
+
return instance
|
78
|
+
|
79
|
+
def get_or_create[K](
|
80
|
+
self,
|
81
|
+
cache: MutableMapping[K, T],
|
82
|
+
key: K,
|
83
|
+
factory: Callable[..., T],
|
84
|
+
) -> T:
|
85
|
+
with suppress(KeyError):
|
86
|
+
return cache[key]
|
87
|
+
|
88
|
+
instance = factory()
|
89
|
+
cache[key] = instance
|
90
|
+
return instance
|
91
|
+
|
92
|
+
|
93
|
+
class SingletonInjectable[T](CachedInjectable[T, T]):
|
56
94
|
__slots__ = ("__dict__",)
|
57
95
|
|
58
96
|
__key: ClassVar[str] = "$instance"
|
@@ -66,32 +104,17 @@ class SingletonInjectable[T](BaseInjectable[T]):
|
|
66
104
|
return self.__dict__
|
67
105
|
|
68
106
|
async def aget_instance(self) -> T:
|
69
|
-
|
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
|
107
|
+
return await self.aget_or_create(self.__cache, self.__key, self.factory.acall)
|
77
108
|
|
78
109
|
def get_instance(self) -> T:
|
79
|
-
|
80
|
-
|
81
|
-
with suppress(KeyError):
|
82
|
-
return cache[self.__key]
|
83
|
-
|
84
|
-
instance = self.factory.call()
|
85
|
-
cache[self.__key] = instance
|
86
|
-
return instance
|
110
|
+
return self.get_or_create(self.__cache, self.__key, self.factory.call)
|
87
111
|
|
88
112
|
def unlock(self) -> None:
|
89
113
|
self.__cache.pop(self.__key, None)
|
90
114
|
|
91
115
|
|
92
116
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
93
|
-
class ScopedInjectable[R, T](
|
94
|
-
factory: Caller[..., R]
|
117
|
+
class ScopedInjectable[R, T](CachedInjectable[R, T], ABC):
|
95
118
|
scope_name: str
|
96
119
|
|
97
120
|
@property
|
@@ -108,29 +131,20 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
108
131
|
|
109
132
|
async def aget_instance(self) -> T:
|
110
133
|
scope = self.get_scope()
|
111
|
-
|
112
|
-
|
113
|
-
return scope.cache[self]
|
114
|
-
|
115
|
-
instance = await self.abuild(scope)
|
116
|
-
self.set_instance(instance, scope)
|
117
|
-
return instance
|
134
|
+
factory = partial(self.abuild, scope)
|
135
|
+
return await self.aget_or_create(scope.cache, self, factory)
|
118
136
|
|
119
137
|
def get_instance(self) -> T:
|
120
138
|
scope = self.get_scope()
|
121
|
-
|
122
|
-
|
123
|
-
return scope.cache[self]
|
124
|
-
|
125
|
-
instance = self.build(scope)
|
126
|
-
self.set_instance(instance, scope)
|
127
|
-
return instance
|
139
|
+
factory = partial(self.build, scope)
|
140
|
+
return self.get_or_create(scope.cache, self, factory)
|
128
141
|
|
129
142
|
def get_scope(self) -> Scope:
|
130
143
|
return get_scope(self.scope_name)
|
131
144
|
|
132
|
-
def
|
133
|
-
scope
|
145
|
+
def setdefault(self, instance: T) -> T:
|
146
|
+
scope = self.get_scope()
|
147
|
+
return self.get_or_create(scope.cache, self, lambda: instance)
|
134
148
|
|
135
149
|
def unlock(self) -> None:
|
136
150
|
if self.is_locked:
|
@@ -174,7 +188,7 @@ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
|
174
188
|
scope.cache.pop(self, None)
|
175
189
|
|
176
190
|
|
177
|
-
@dataclass(repr=False, frozen=True, slots=True)
|
191
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
178
192
|
class ShouldBeInjectable[T](Injectable[T]):
|
179
193
|
cls: type[T]
|
180
194
|
|
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=w23JjZyNZBtWSTCtKHEje-YC6m5qf_LQ1By0qQDh6zY,5360
|
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.dist-info/METADATA,sha256=uRWPRk__XYHBvfjfwrMCJMxYHdIX9EOlE8mh_qWcsDQ,3199
|
24
|
+
python_injection-0.14.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
python_injection-0.14.6.dist-info/RECORD,,
|
File without changes
|