python-injection 0.18.5__tar.gz → 0.18.6.post0__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.
Files changed (29) hide show
  1. {python_injection-0.18.5 → python_injection-0.18.6.post0}/PKG-INFO +1 -1
  2. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/__init__.pyi +0 -2
  3. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/module.py +0 -11
  4. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/entrypoint.py +22 -17
  5. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/loaders.py +98 -14
  6. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/testing/__init__.py +5 -5
  7. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/testing/__init__.pyi +4 -3
  8. {python_injection-0.18.5 → python_injection-0.18.6.post0}/pyproject.toml +1 -1
  9. {python_injection-0.18.5 → python_injection-0.18.6.post0}/.gitignore +0 -0
  10. {python_injection-0.18.5 → python_injection-0.18.6.post0}/LICENSE +0 -0
  11. {python_injection-0.18.5 → python_injection-0.18.6.post0}/README.md +0 -0
  12. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/__init__.py +0 -0
  13. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/__init__.py +0 -0
  14. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/__init__.py +0 -0
  15. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/asynchronous.py +0 -0
  16. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/event.py +0 -0
  17. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/invertible.py +0 -0
  18. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/key.py +0 -0
  19. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/lazy.py +0 -0
  20. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/common/type.py +0 -0
  21. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/descriptors.py +0 -0
  22. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/injectables.py +0 -0
  23. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/scope.py +0 -0
  24. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/_core/slots.py +0 -0
  25. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/exceptions.py +0 -0
  26. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/ext/__init__.py +0 -0
  27. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/ext/fastapi.py +0 -0
  28. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/ext/fastapi.pyi +0 -0
  29. {python_injection-0.18.5 → python_injection-0.18.6.post0}/injection/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.18.5
3
+ Version: 0.18.6.post0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -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
@@ -817,17 +817,6 @@ class Module(Broker, EventListener):
817
817
  for broker in self.__brokers:
818
818
  broker.unsafe_unlocking()
819
819
 
820
- def load_profile(self, *names: str) -> ContextManager[Self]:
821
- modules = (self.from_name(name) for name in names)
822
- self.unlock().init_modules(*modules)
823
-
824
- @contextmanager
825
- def unload() -> Iterator[Self]:
826
- yield self
827
- self.unlock().init_modules()
828
-
829
- return unload()
830
-
831
820
  async def all_ready(self) -> None:
832
821
  for broker in self.__brokers:
833
822
  await broker.all_ready()
@@ -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, mod
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
- module: Module = ...,
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
- module: Module = ...,
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
- module: Module | None = None,
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, module)
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
- module: Module = field(default_factory=mod)
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.module.make_injected_function)
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, /, *names: str) -> Self:
112
+ def load_profile(self, name: str, /) -> Self:
109
113
  @contextmanager
110
- def decorator(module: Module) -> Iterator[None]:
111
- with module.load_profile(*names):
114
+ def decorator(loader: ProfileLoader) -> Iterator[None]:
115
+ with loader.load(name):
112
116
  yield
113
117
 
114
- return self.decorate(decorator(self.module))
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.module)
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
- module: Module | None = None,
150
+ profile_loader: ProfileLoader | None = None,
147
151
  ) -> EntrypointDecorator[P, T, _T]:
148
- module = module or mod()
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
- self = cls(function, module)
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 collections.abc import Callable, Iterator, Mapping
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, ContextManager, Self
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
- def load_profile(*names: str) -> ContextManager[Module]:
19
- """
20
- Injection module initialization function based on profile name.
21
- A profile name is equivalent to an injection module name.
22
- """
23
-
24
- return mod().load_profile(*names)
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
+ module_subsets: 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_subsets_for(self.module)
150
+ return self
151
+
152
+ def load(self, name: str, /) -> LoadedProfile:
153
+ self.init()
154
+ target_module = self.__init_subsets_for(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_subsets_for(self, module: Module) -> Module:
162
+ if not self.__is_initialized(module):
163
+ target_modules = tuple(
164
+ self.__init_subsets_for(mod(name))
165
+ for name in self.module_subsets.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 ContextManager, Final
1
+ from typing import Final
2
2
 
3
- from injection import Module, mod
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(*names: str) -> ContextManager[Module]:
29
- return load_profile(_TEST_PROFILE_NAME, *names)
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 ContextManager, Final
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(*names: str) -> ContextManager[Module]:
16
+ def load_test_profile(loader: ProfileLoader = ...) -> LoadedProfile:
16
17
  """
17
- Context manager or decorator for temporary use test module.
18
+ Context manager for temporary use test module.
18
19
  """
@@ -29,7 +29,7 @@ test = [
29
29
 
30
30
  [project]
31
31
  name = "python-injection"
32
- version = "0.18.5"
32
+ version = "0.18.6.post0"
33
33
  description = "Fast and easy dependency injection framework."
34
34
  license = "MIT"
35
35
  license-files = ["LICENSE"]