python-injection 0.14.6.post2__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.post2 → python_injection-0.15.0}/PKG-INFO +1 -1
  2. python_injection-0.15.0/injection/utils.py +130 -0
  3. {python_injection-0.14.6.post2 → python_injection-0.15.0}/pyproject.toml +1 -1
  4. python_injection-0.14.6.post2/injection/utils.py +0 -96
  5. {python_injection-0.14.6.post2 → python_injection-0.15.0}/.gitignore +0 -0
  6. {python_injection-0.14.6.post2 → python_injection-0.15.0}/README.md +0 -0
  7. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/__init__.py +0 -0
  8. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/__init__.pyi +0 -0
  9. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/__init__.py +0 -0
  10. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/__init__.py +0 -0
  11. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/asynchronous.py +0 -0
  12. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/event.py +0 -0
  13. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/invertible.py +0 -0
  14. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/key.py +0 -0
  15. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/lazy.py +0 -0
  16. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/common/type.py +0 -0
  17. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/descriptors.py +0 -0
  18. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/injectables.py +0 -0
  19. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/module.py +0 -0
  20. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/scope.py +0 -0
  21. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/_core/slots.py +0 -0
  22. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/exceptions.py +0 -0
  23. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/integrations/__init__.py +0 -0
  24. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/integrations/fastapi.py +0 -0
  25. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/py.typed +0 -0
  26. {python_injection-0.14.6.post2 → python_injection-0.15.0}/injection/testing/__init__.py +0 -0
  27. {python_injection-0.14.6.post2 → 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.post2
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
@@ -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.post2"
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)