python-injection 0.18.4__tar.gz → 0.18.6__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.18.4 → python_injection-0.18.6}/PKG-INFO +1 -1
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/__init__.pyi +0 -2
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/module.py +18 -18
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/entrypoint.py +22 -17
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/loaders.py +98 -14
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/testing/__init__.py +5 -5
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/testing/__init__.pyi +4 -3
- {python_injection-0.18.4 → python_injection-0.18.6}/pyproject.toml +1 -1
- {python_injection-0.18.4 → python_injection-0.18.6}/.gitignore +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/LICENSE +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/README.md +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/__init__.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/__init__.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/asynchronous.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/event.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/invertible.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/key.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/common/type.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/descriptors.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/injectables.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/scope.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/_core/slots.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/exceptions.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/ext/__init__.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/ext/fastapi.py +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/ext/fastapi.pyi +0 -0
- {python_injection-0.18.4 → python_injection-0.18.6}/injection/py.typed +0 -0
@@ -343,8 +343,6 @@ class Module:
|
|
343
343
|
Function to unlock the module by deleting cached instances of singletons.
|
344
344
|
"""
|
345
345
|
|
346
|
-
@contextmanager
|
347
|
-
def load_profile(self, *names: str) -> Iterator[Self]: ...
|
348
346
|
async def all_ready(self) -> None: ...
|
349
347
|
def add_logger(self, logger: Logger) -> Self: ...
|
350
348
|
@classmethod
|
@@ -156,6 +156,12 @@ class ModulePriorityUpdated(ModuleEvent):
|
|
156
156
|
)
|
157
157
|
|
158
158
|
|
159
|
+
@dataclass(frozen=True, slots=True)
|
160
|
+
class UnlockCalled(Event):
|
161
|
+
def __str__(self) -> str:
|
162
|
+
return "An `unlock` method has been called."
|
163
|
+
|
164
|
+
|
159
165
|
"""
|
160
166
|
Broker
|
161
167
|
"""
|
@@ -179,7 +185,7 @@ class Broker(Protocol):
|
|
179
185
|
raise NotImplementedError
|
180
186
|
|
181
187
|
@abstractmethod
|
182
|
-
def
|
188
|
+
def unsafe_unlocking(self) -> None:
|
183
189
|
raise NotImplementedError
|
184
190
|
|
185
191
|
@abstractmethod
|
@@ -289,12 +295,10 @@ class Locator(Broker):
|
|
289
295
|
|
290
296
|
return self
|
291
297
|
|
292
|
-
def
|
298
|
+
def unsafe_unlocking(self) -> None:
|
293
299
|
for injectable in self.__injectables:
|
294
300
|
injectable.unlock()
|
295
301
|
|
296
|
-
return self
|
297
|
-
|
298
302
|
async def all_ready(self) -> None:
|
299
303
|
for injectable in self.__injectables:
|
300
304
|
if injectable.is_locked:
|
@@ -802,21 +806,16 @@ class Module(Broker, EventListener):
|
|
802
806
|
return self
|
803
807
|
|
804
808
|
def unlock(self) -> Self:
|
805
|
-
|
806
|
-
broker.unlock()
|
807
|
-
|
808
|
-
return self
|
809
|
+
event = UnlockCalled()
|
809
810
|
|
810
|
-
|
811
|
-
|
812
|
-
self.unlock().init_modules(*modules)
|
811
|
+
with self.dispatch(event, lock_bypass=True):
|
812
|
+
self.unsafe_unlocking()
|
813
813
|
|
814
|
-
|
815
|
-
def unload() -> Iterator[Self]:
|
816
|
-
yield self
|
817
|
-
self.unlock().init_modules()
|
814
|
+
return self
|
818
815
|
|
819
|
-
|
816
|
+
def unsafe_unlocking(self) -> None:
|
817
|
+
for broker in self.__brokers:
|
818
|
+
broker.unsafe_unlocking()
|
820
819
|
|
821
820
|
async def all_ready(self) -> None:
|
822
821
|
for broker in self.__brokers:
|
@@ -839,8 +838,9 @@ class Module(Broker, EventListener):
|
|
839
838
|
return self.dispatch(self_event)
|
840
839
|
|
841
840
|
@contextmanager
|
842
|
-
def dispatch(self, event: Event) -> Iterator[None]:
|
843
|
-
|
841
|
+
def dispatch(self, event: Event, *, lock_bypass: bool = False) -> Iterator[None]:
|
842
|
+
if not lock_bypass:
|
843
|
+
self.__check_locking()
|
844
844
|
|
845
845
|
with self.__channel.dispatch(event):
|
846
846
|
try:
|
@@ -9,8 +9,8 @@ from types import MethodType
|
|
9
9
|
from types import ModuleType as PythonModule
|
10
10
|
from typing import Any, Self, final, overload
|
11
11
|
|
12
|
-
from injection import Module
|
13
|
-
from injection.loaders import PythonModuleLoader
|
12
|
+
from injection import Module
|
13
|
+
from injection.loaders import ProfileLoader, PythonModuleLoader
|
14
14
|
|
15
15
|
__all__ = ("AsyncEntrypoint", "Entrypoint", "autocall", "entrypointmaker")
|
16
16
|
|
@@ -35,7 +35,7 @@ def entrypointmaker[*Ts, **P, T1, T2](
|
|
35
35
|
wrapped: EntrypointSetupMethod[*Ts, P, T1, T2],
|
36
36
|
/,
|
37
37
|
*,
|
38
|
-
|
38
|
+
profile_loader: ProfileLoader = ...,
|
39
39
|
) -> EntrypointDecorator[P, T1, T2]: ...
|
40
40
|
|
41
41
|
|
@@ -44,7 +44,7 @@ def entrypointmaker[*Ts, **P, T1, T2](
|
|
44
44
|
wrapped: None = ...,
|
45
45
|
/,
|
46
46
|
*,
|
47
|
-
|
47
|
+
profile_loader: ProfileLoader = ...,
|
48
48
|
) -> Callable[
|
49
49
|
[EntrypointSetupMethod[*Ts, P, T1, T2]],
|
50
50
|
EntrypointDecorator[P, T1, T2],
|
@@ -55,12 +55,12 @@ def entrypointmaker[*Ts, **P, T1, T2](
|
|
55
55
|
wrapped: EntrypointSetupMethod[*Ts, P, T1, T2] | None = None,
|
56
56
|
/,
|
57
57
|
*,
|
58
|
-
|
58
|
+
profile_loader: ProfileLoader | None = None,
|
59
59
|
) -> Any:
|
60
60
|
def decorator(
|
61
61
|
wp: EntrypointSetupMethod[*Ts, P, T1, T2],
|
62
62
|
) -> EntrypointDecorator[P, T1, T2]:
|
63
|
-
return Entrypoint._make_decorator(wp,
|
63
|
+
return Entrypoint._make_decorator(wp, profile_loader)
|
64
64
|
|
65
65
|
return decorator(wrapped) if wrapped else decorator
|
66
66
|
|
@@ -69,11 +69,15 @@ def entrypointmaker[*Ts, **P, T1, T2](
|
|
69
69
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
70
70
|
class Entrypoint[**P, T]:
|
71
71
|
function: Callable[P, T]
|
72
|
-
|
72
|
+
profile_loader: ProfileLoader = field(default_factory=ProfileLoader)
|
73
73
|
|
74
74
|
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
75
75
|
return self.function(*args, **kwargs)
|
76
76
|
|
77
|
+
@property
|
78
|
+
def __module(self) -> Module:
|
79
|
+
return self.profile_loader.module
|
80
|
+
|
77
81
|
def async_to_sync[_T](
|
78
82
|
self: AsyncEntrypoint[P, _T],
|
79
83
|
run: Callable[[Coroutine[Any, Any, _T]], _T] = asyncio.run,
|
@@ -95,7 +99,7 @@ class Entrypoint[**P, T]:
|
|
95
99
|
return self.__recreate(decorator(self.function))
|
96
100
|
|
97
101
|
def inject(self) -> Self:
|
98
|
-
return self.decorate(self.
|
102
|
+
return self.decorate(self.__module.make_injected_function)
|
99
103
|
|
100
104
|
def load_modules(
|
101
105
|
self,
|
@@ -105,13 +109,13 @@ class Entrypoint[**P, T]:
|
|
105
109
|
) -> Self:
|
106
110
|
return self.setup(lambda: loader.load(*packages))
|
107
111
|
|
108
|
-
def load_profile(self,
|
112
|
+
def load_profile(self, name: str, /) -> Self:
|
109
113
|
@contextmanager
|
110
|
-
def decorator(
|
111
|
-
with
|
114
|
+
def decorator(loader: ProfileLoader) -> Iterator[None]:
|
115
|
+
with loader.load(name):
|
112
116
|
yield
|
113
117
|
|
114
|
-
return self.decorate(decorator(self.
|
118
|
+
return self.decorate(decorator(self.profile_loader))
|
115
119
|
|
116
120
|
def setup(self, function: Callable[..., Any], /) -> Self:
|
117
121
|
@contextmanager
|
@@ -136,20 +140,21 @@ class Entrypoint[**P, T]:
|
|
136
140
|
function: Callable[_P, _T],
|
137
141
|
/,
|
138
142
|
) -> Entrypoint[_P, _T]:
|
139
|
-
return type(self)(function, self.
|
143
|
+
return type(self)(function, self.profile_loader)
|
140
144
|
|
141
145
|
@classmethod
|
142
146
|
def _make_decorator[*Ts, _T](
|
143
147
|
cls,
|
144
148
|
setup_method: EntrypointSetupMethod[*Ts, P, T, _T],
|
145
149
|
/,
|
146
|
-
|
150
|
+
profile_loader: ProfileLoader | None = None,
|
147
151
|
) -> EntrypointDecorator[P, T, _T]:
|
148
|
-
|
149
|
-
setup_method = module.make_injected_function(setup_method)
|
152
|
+
profile_loader = profile_loader or ProfileLoader()
|
153
|
+
setup_method = profile_loader.module.make_injected_function(setup_method)
|
150
154
|
|
151
155
|
def decorator(function: Callable[P, T]) -> Callable[P, _T]:
|
152
|
-
|
156
|
+
profile_loader.init()
|
157
|
+
self = cls(function, profile_loader)
|
153
158
|
return MethodType(setup_method, self)().function
|
154
159
|
|
155
160
|
return decorator
|
@@ -1,27 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import itertools
|
2
4
|
import sys
|
3
|
-
from
|
5
|
+
from abc import abstractmethod
|
6
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
4
7
|
from dataclasses import dataclass, field
|
5
8
|
from importlib import import_module
|
6
9
|
from importlib.util import find_spec
|
7
10
|
from os.path import isfile
|
8
11
|
from pkgutil import walk_packages
|
9
|
-
from types import MappingProxyType
|
12
|
+
from types import MappingProxyType, TracebackType
|
10
13
|
from types import ModuleType as PythonModule
|
11
|
-
from typing import ClassVar,
|
12
|
-
|
13
|
-
from injection import Module, mod
|
14
|
-
|
15
|
-
__all__ = ("PythonModuleLoader", "load_packages", "load_profile")
|
14
|
+
from typing import ClassVar, Protocol, Self, runtime_checkable
|
16
15
|
|
16
|
+
from injection import Module, Priority, mod
|
17
17
|
|
18
|
-
|
19
|
-
""
|
20
|
-
|
21
|
-
|
22
|
-
""
|
23
|
-
|
24
|
-
|
18
|
+
__all__ = (
|
19
|
+
"LoadedProfile",
|
20
|
+
"ProfileLoader",
|
21
|
+
"PythonModuleLoader",
|
22
|
+
"load_packages",
|
23
|
+
"load_profile",
|
24
|
+
)
|
25
25
|
|
26
26
|
|
27
27
|
def load_packages(
|
@@ -36,6 +36,15 @@ def load_packages(
|
|
36
36
|
return PythonModuleLoader(predicate).load(*packages).modules
|
37
37
|
|
38
38
|
|
39
|
+
def load_profile(name: str, /, loader: ProfileLoader | None = None) -> LoadedProfile:
|
40
|
+
"""
|
41
|
+
Injection module initialization function based on a profile name.
|
42
|
+
A profile name is equivalent to an injection module name.
|
43
|
+
"""
|
44
|
+
|
45
|
+
return (loader or ProfileLoader()).load(name)
|
46
|
+
|
47
|
+
|
39
48
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
40
49
|
class PythonModuleLoader:
|
41
50
|
predicate: Callable[[str], bool]
|
@@ -128,3 +137,78 @@ class PythonModuleLoader:
|
|
128
137
|
return any(script_name.endswith(suffix) for suffix in suffixes)
|
129
138
|
|
130
139
|
return cls(predicate)
|
140
|
+
|
141
|
+
|
142
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
143
|
+
class ProfileLoader:
|
144
|
+
dependencies: Mapping[str, Sequence[str]] = field(default=MappingProxyType({}))
|
145
|
+
module: Module = field(default_factory=mod, kw_only=True)
|
146
|
+
__initialized_modules: set[str] = field(default_factory=set, init=False)
|
147
|
+
|
148
|
+
def init(self) -> Self:
|
149
|
+
self.__init_module_dependencies(self.module)
|
150
|
+
return self
|
151
|
+
|
152
|
+
def load(self, name: str, /) -> LoadedProfile:
|
153
|
+
self.init()
|
154
|
+
target_module = self.__init_module_dependencies(mod(name))
|
155
|
+
self.module.use(target_module, priority=Priority.HIGH)
|
156
|
+
return _UserLoadedProfile(self, name)
|
157
|
+
|
158
|
+
def _unload(self, name: str, /) -> None:
|
159
|
+
self.module.unlock().stop_using(mod(name))
|
160
|
+
|
161
|
+
def __init_module_dependencies(self, module: Module) -> Module:
|
162
|
+
if not self.__is_initialized(module):
|
163
|
+
target_modules = tuple(
|
164
|
+
self.__init_module_dependencies(mod(profile_name))
|
165
|
+
for profile_name in self.dependencies.get(module.name, ())
|
166
|
+
)
|
167
|
+
module.unlock().init_modules(*target_modules)
|
168
|
+
self.__mark_initialized(module)
|
169
|
+
|
170
|
+
return module
|
171
|
+
|
172
|
+
def __is_initialized(self, module: Module) -> bool:
|
173
|
+
return module.name in self.__initialized_modules
|
174
|
+
|
175
|
+
def __mark_initialized(self, module: Module) -> None:
|
176
|
+
self.__initialized_modules.add(module.name)
|
177
|
+
|
178
|
+
|
179
|
+
@runtime_checkable
|
180
|
+
class LoadedProfile(Protocol):
|
181
|
+
__slots__ = ()
|
182
|
+
|
183
|
+
def __enter__(self) -> Self:
|
184
|
+
return self
|
185
|
+
|
186
|
+
def __exit__(
|
187
|
+
self,
|
188
|
+
exc_type: type[BaseException] | None,
|
189
|
+
exc_value: BaseException | None,
|
190
|
+
traceback: TracebackType | None,
|
191
|
+
) -> None:
|
192
|
+
self.unload()
|
193
|
+
|
194
|
+
@abstractmethod
|
195
|
+
def reload(self) -> Self:
|
196
|
+
raise NotImplementedError
|
197
|
+
|
198
|
+
@abstractmethod
|
199
|
+
def unload(self) -> Self:
|
200
|
+
raise NotImplementedError
|
201
|
+
|
202
|
+
|
203
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
204
|
+
class _UserLoadedProfile(LoadedProfile):
|
205
|
+
loader: ProfileLoader
|
206
|
+
name: str
|
207
|
+
|
208
|
+
def reload(self) -> Self:
|
209
|
+
self.loader.load(self.name)
|
210
|
+
return self
|
211
|
+
|
212
|
+
def unload(self) -> Self:
|
213
|
+
self.loader._unload(self.name)
|
214
|
+
return self
|
@@ -1,7 +1,7 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Final
|
2
2
|
|
3
|
-
from injection import
|
4
|
-
from injection.loaders import load_profile
|
3
|
+
from injection import mod
|
4
|
+
from injection.loaders import LoadedProfile, ProfileLoader, load_profile
|
5
5
|
|
6
6
|
__all__ = (
|
7
7
|
"load_test_profile",
|
@@ -25,5 +25,5 @@ test_scoped = mod(_TEST_PROFILE_NAME).scoped
|
|
25
25
|
test_singleton = mod(_TEST_PROFILE_NAME).singleton
|
26
26
|
|
27
27
|
|
28
|
-
def load_test_profile(
|
29
|
-
return load_profile(_TEST_PROFILE_NAME,
|
28
|
+
def load_test_profile(loader: ProfileLoader | None = None) -> LoadedProfile:
|
29
|
+
return load_profile(_TEST_PROFILE_NAME, loader)
|
@@ -1,6 +1,7 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Final
|
2
2
|
|
3
3
|
from injection import Module
|
4
|
+
from injection.loaders import LoadedProfile, ProfileLoader
|
4
5
|
|
5
6
|
__MODULE: Final[Module] = ...
|
6
7
|
|
@@ -12,7 +13,7 @@ test_injectable = __MODULE.injectable
|
|
12
13
|
test_scoped = __MODULE.scoped
|
13
14
|
test_singleton = __MODULE.singleton
|
14
15
|
|
15
|
-
def load_test_profile(
|
16
|
+
def load_test_profile(loader: ProfileLoader = ...) -> LoadedProfile:
|
16
17
|
"""
|
17
|
-
Context manager
|
18
|
+
Context manager for temporary use test module.
|
18
19
|
"""
|
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
|
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
|
File without changes
|