python-injection 0.14.6.post1__tar.gz → 0.15.0__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 (27) hide show
  1. {python_injection-0.14.6.post1 → python_injection-0.15.0}/PKG-INFO +1 -1
  2. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/invertible.py +2 -0
  3. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/module.py +2 -2
  4. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/scope.py +3 -3
  5. python_injection-0.15.0/injection/utils.py +130 -0
  6. {python_injection-0.14.6.post1 → python_injection-0.15.0}/pyproject.toml +1 -1
  7. python_injection-0.14.6.post1/injection/utils.py +0 -96
  8. {python_injection-0.14.6.post1 → python_injection-0.15.0}/.gitignore +0 -0
  9. {python_injection-0.14.6.post1 → python_injection-0.15.0}/README.md +0 -0
  10. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/__init__.py +0 -0
  11. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/__init__.pyi +0 -0
  12. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/__init__.py +0 -0
  13. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/__init__.py +0 -0
  14. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/asynchronous.py +0 -0
  15. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/event.py +0 -0
  16. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/key.py +0 -0
  17. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/lazy.py +0 -0
  18. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/type.py +0 -0
  19. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/descriptors.py +0 -0
  20. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/injectables.py +0 -0
  21. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/slots.py +0 -0
  22. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/exceptions.py +0 -0
  23. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/integrations/__init__.py +0 -0
  24. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/integrations/fastapi.py +0 -0
  25. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/py.typed +0 -0
  26. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/testing/__init__.py +0 -0
  27. {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/testing/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.14.6.post1
3
+ Version: 0.15.0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -6,6 +6,8 @@ from typing import Protocol, runtime_checkable
6
6
 
7
7
  @runtime_checkable
8
8
  class Invertible[T](Protocol):
9
+ __slots__ = ()
10
+
9
11
  @abstractmethod
10
12
  def __invert__(self) -> T:
11
13
  raise NotImplementedError
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import threading
3
4
  from abc import ABC, abstractmethod
4
5
  from collections import OrderedDict, deque
5
6
  from collections.abc import (
@@ -27,7 +28,6 @@ from inspect import (
27
28
  )
28
29
  from inspect import signature as inspect_signature
29
30
  from logging import Logger, getLogger
30
- from threading import Lock
31
31
  from types import MethodType
32
32
  from typing import (
33
33
  Any,
@@ -982,7 +982,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
982
982
 
983
983
  def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
984
984
  self.__dependencies = Dependencies.empty()
985
- self.__lock = Lock() if threadsafe else nullcontext()
985
+ self.__lock = threading.Lock() if threadsafe else nullcontext()
986
986
  self.__owner = None
987
987
  self.__tasks = deque()
988
988
  self.__wrapped = wrapped
@@ -157,18 +157,18 @@ def get_scope(name, default=...): # type: ignore[no-untyped-def]
157
157
  def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
158
158
  if shared:
159
159
  is_already_defined = bool(get_active_scopes(name))
160
- state = __SHARED_SCOPES[name]
160
+ states = __SHARED_SCOPES
161
161
 
162
162
  else:
163
163
  is_already_defined = bool(get_scope(name, default=None))
164
- state = __CONTEXTUAL_SCOPES[name]
164
+ states = __CONTEXTUAL_SCOPES
165
165
 
166
166
  if is_already_defined:
167
167
  raise ScopeAlreadyDefinedError(
168
168
  f"Scope `{name}` is already defined in the current context."
169
169
  )
170
170
 
171
- with state.bind(scope):
171
+ with states[name].bind(scope):
172
172
  yield
173
173
 
174
174
 
@@ -0,0 +1,130 @@
1
+ import itertools
2
+ import sys
3
+ from collections.abc import Callable, Iterator, Mapping
4
+ from dataclasses import dataclass, field
5
+ from importlib import import_module
6
+ from importlib.util import find_spec
7
+ from os.path import isfile
8
+ from pkgutil import walk_packages
9
+ from types import MappingProxyType
10
+ 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")
16
+
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)
25
+
26
+
27
+ def load_packages(
28
+ *packages: PythonModule | str,
29
+ predicate: Callable[[str], bool] = lambda module_name: True,
30
+ ) -> dict[str, PythonModule]:
31
+ """
32
+ Function for importing all modules in a Python package.
33
+ Pass the `predicate` parameter if you want to filter the modules to be imported.
34
+ """
35
+
36
+ return PythonModuleLoader(predicate).load(*packages).modules
37
+
38
+
39
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
40
+ class PythonModuleLoader:
41
+ predicate: Callable[[str], bool]
42
+ __modules: dict[str, PythonModule | None] = field(
43
+ default_factory=dict,
44
+ init=False,
45
+ )
46
+
47
+ # To easily mock `sys.modules` in tests
48
+ _sys_modules: ClassVar[Mapping[str, PythonModule]] = MappingProxyType(sys.modules)
49
+
50
+ @property
51
+ def modules(self) -> dict[str, PythonModule]:
52
+ return {
53
+ name: module
54
+ for name, module in self.__modules.items()
55
+ if module is not None
56
+ }
57
+
58
+ def load(self, *packages: PythonModule | str) -> Self:
59
+ modules = itertools.chain.from_iterable(
60
+ self.__iter_modules(package) for package in packages
61
+ )
62
+ self.__modules.update(modules)
63
+ return self
64
+
65
+ def __is_already_loaded(self, module_name: str) -> bool:
66
+ return any(
67
+ module_name in modules for modules in (self.__modules, self._sys_modules)
68
+ )
69
+
70
+ def __iter_modules(
71
+ self,
72
+ package: PythonModule | str,
73
+ ) -> Iterator[tuple[str, PythonModule | None]]:
74
+ if isinstance(package, str):
75
+ package = import_module(package)
76
+
77
+ package_name = package.__name__
78
+
79
+ try:
80
+ package_path = package.__path__
81
+ except AttributeError as exc:
82
+ raise TypeError(f"`{package_name}` isn't Python package.") from exc
83
+
84
+ for info in walk_packages(path=package_path, prefix=f"{package_name}."):
85
+ name = info.name
86
+
87
+ if info.ispkg or self.__is_already_loaded(name):
88
+ continue
89
+
90
+ module = import_module(name) if self.predicate(name) else None
91
+ yield name, module
92
+
93
+ @classmethod
94
+ def from_keywords(cls, *keywords: str) -> Self:
95
+ """
96
+ Create loader to import modules from a Python package if one of the keywords is
97
+ contained in the Python script.
98
+ """
99
+
100
+ def predicate(module_name: str) -> bool:
101
+ spec = find_spec(module_name)
102
+
103
+ if spec is None:
104
+ return False
105
+
106
+ module_path = spec.origin
107
+
108
+ if module_path is None or not isfile(module_path):
109
+ return False
110
+
111
+ with open(module_path, "r") as script:
112
+ return any(keyword in line for line in script for keyword in keywords)
113
+
114
+ return cls(predicate)
115
+
116
+ @classmethod
117
+ def startswith(cls, *prefixes: str) -> Self:
118
+ def predicate(module_name: str) -> bool:
119
+ script_name = module_name.split(".")[-1]
120
+ return any(script_name.startswith(prefix) for prefix in prefixes)
121
+
122
+ return cls(predicate)
123
+
124
+ @classmethod
125
+ def endswith(cls, *suffixes: str) -> Self:
126
+ def predicate(module_name: str) -> bool:
127
+ script_name = module_name.split(".")[-1]
128
+ return any(script_name.endswith(suffix) for suffix in suffixes)
129
+
130
+ return cls(predicate)
@@ -25,7 +25,7 @@ test = [
25
25
 
26
26
  [project]
27
27
  name = "python-injection"
28
- version = "0.14.6.post1"
28
+ version = "0.15.0"
29
29
  description = "Fast and easy dependency injection framework."
30
30
  license = { text = "MIT" }
31
31
  readme = "README.md"
@@ -1,96 +0,0 @@
1
- import sys
2
- from collections.abc import Callable, Collection, Iterator
3
- from importlib import import_module
4
- from importlib.util import find_spec
5
- from pkgutil import walk_packages
6
- from types import ModuleType as PythonModule
7
- from typing import ContextManager
8
-
9
- from injection import Module, mod
10
- from injection import __name__ as injection_package_name
11
-
12
- __all__ = ("load_modules_with_keywords", "load_packages", "load_profile")
13
-
14
-
15
- def load_profile(*names: str) -> ContextManager[Module]:
16
- """
17
- Injection module initialization function based on profile name.
18
- A profile name is equivalent to an injection module name.
19
- """
20
-
21
- return mod().load_profile(*names)
22
-
23
-
24
- def load_modules_with_keywords(
25
- *packages: PythonModule | str,
26
- keywords: Collection[str] | None = None,
27
- ) -> dict[str, PythonModule]:
28
- """
29
- Function to import modules from a Python package if one of the keywords is contained in the Python script.
30
- The default keywords are:
31
- - `from injection `
32
- - `from injection.`
33
- - `import injection`
34
- """
35
-
36
- if keywords is None:
37
- keywords = (
38
- f"from {injection_package_name} ",
39
- f"from {injection_package_name}.",
40
- f"import {injection_package_name}",
41
- )
42
-
43
- def predicate(module_name: str) -> bool:
44
- spec = find_spec(module_name)
45
-
46
- if spec and (module_path := spec.origin):
47
- with open(module_path, "r") as file:
48
- python_script = file.read()
49
-
50
- return bool(python_script) and any(
51
- keyword in python_script for keyword in keywords
52
- )
53
-
54
- return False
55
-
56
- return load_packages(*packages, predicate=predicate)
57
-
58
-
59
- def load_packages(
60
- *packages: PythonModule | str,
61
- predicate: Callable[[str], bool] = lambda module_name: True,
62
- ) -> dict[str, PythonModule]:
63
- """
64
- Function for importing all modules in a Python package.
65
- Pass the `predicate` parameter if you want to filter the modules to be imported.
66
- """
67
-
68
- loaded: dict[str, PythonModule] = {}
69
-
70
- for package in packages:
71
- if isinstance(package, str):
72
- package = import_module(package)
73
-
74
- loaded |= __iter_modules_from(package, predicate)
75
-
76
- return loaded
77
-
78
-
79
- def __iter_modules_from(
80
- package: PythonModule,
81
- predicate: Callable[[str], bool],
82
- ) -> Iterator[tuple[str, PythonModule]]:
83
- package_name = package.__name__
84
-
85
- try:
86
- package_path = package.__path__
87
- except AttributeError as exc:
88
- raise TypeError(f"`{package_name}` isn't Python package.") from exc
89
-
90
- for info in walk_packages(path=package_path, prefix=f"{package_name}."):
91
- name = info.name
92
-
93
- if info.ispkg or name in sys.modules or not predicate(name):
94
- continue
95
-
96
- yield name, import_module(name)