python-injection 0.14.6.post2__py3-none-any.whl → 0.15.0__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/utils.py CHANGED
@@ -1,15 +1,18 @@
1
+ import itertools
1
2
  import sys
2
- from collections.abc import Callable, Collection, Iterator
3
+ from collections.abc import Callable, Iterator, Mapping
4
+ from dataclasses import dataclass, field
3
5
  from importlib import import_module
4
6
  from importlib.util import find_spec
7
+ from os.path import isfile
5
8
  from pkgutil import walk_packages
9
+ from types import MappingProxyType
6
10
  from types import ModuleType as PythonModule
7
- from typing import ContextManager
11
+ from typing import ClassVar, ContextManager, Self
8
12
 
9
13
  from injection import Module, mod
10
- from injection import __name__ as injection_package_name
11
14
 
12
- __all__ = ("load_modules_with_keywords", "load_packages", "load_profile")
15
+ __all__ = ("PythonModuleLoader", "load_packages", "load_profile")
13
16
 
14
17
 
15
18
  def load_profile(*names: str) -> ContextManager[Module]:
@@ -21,76 +24,107 @@ def load_profile(*names: str) -> ContextManager[Module]:
21
24
  return mod().load_profile(*names)
22
25
 
23
26
 
24
- def load_modules_with_keywords(
27
+ def load_packages(
25
28
  *packages: PythonModule | str,
26
- keywords: Collection[str] | None = None,
29
+ predicate: Callable[[str], bool] = lambda module_name: True,
27
30
  ) -> dict[str, PythonModule]:
28
31
  """
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`
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
34
  """
35
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}",
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
41
61
  )
62
+ self.__modules.update(modules)
63
+ return self
42
64
 
43
- def predicate(module_name: str) -> bool:
44
- spec = find_spec(module_name)
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
+ )
45
69
 
46
- if spec and (module_path := spec.origin):
47
- with open(module_path, "r") as file:
48
- python_script = file.read()
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)
49
76
 
50
- return bool(python_script) and any(
51
- keyword in python_script for keyword in keywords
52
- )
77
+ package_name = package.__name__
53
78
 
54
- return False
79
+ try:
80
+ package_path = package.__path__
81
+ except AttributeError as exc:
82
+ raise TypeError(f"`{package_name}` isn't Python package.") from exc
55
83
 
56
- return load_packages(*packages, predicate=predicate)
84
+ for info in walk_packages(path=package_path, prefix=f"{package_name}."):
85
+ name = info.name
57
86
 
87
+ if info.ispkg or self.__is_already_loaded(name):
88
+ continue
58
89
 
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
- """
90
+ module = import_module(name) if self.predicate(name) else None
91
+ yield name, module
67
92
 
68
- loaded: dict[str, PythonModule] = {}
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
+ """
69
99
 
70
- for package in packages:
71
- if isinstance(package, str):
72
- package = import_module(package)
100
+ def predicate(module_name: str) -> bool:
101
+ spec = find_spec(module_name)
102
+
103
+ if spec is None:
104
+ return False
73
105
 
74
- loaded |= __iter_modules_from(package, predicate)
106
+ module_path = spec.origin
75
107
 
76
- return loaded
108
+ if module_path is None or not isfile(module_path):
109
+ return False
77
110
 
111
+ with open(module_path, "r") as script:
112
+ return any(keyword in line for line in script for keyword in keywords)
78
113
 
79
- def __iter_modules_from(
80
- package: PythonModule,
81
- predicate: Callable[[str], bool],
82
- ) -> Iterator[tuple[str, PythonModule]]:
83
- package_name = package.__name__
114
+ return cls(predicate)
84
115
 
85
- try:
86
- package_path = package.__path__
87
- except AttributeError as exc:
88
- raise TypeError(f"`{package_name}` isn't Python package.") from exc
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)
89
121
 
90
- for info in walk_packages(path=package_path, prefix=f"{package_name}."):
91
- name = info.name
122
+ return cls(predicate)
92
123
 
93
- if info.ispkg or name in sys.modules or not predicate(name):
94
- continue
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)
95
129
 
96
- yield name, import_module(name)
130
+ return cls(predicate)
@@ -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
@@ -2,7 +2,7 @@ injection/__init__.py,sha256=a-rBAlBTiH6TzZ-11NGK6diovfibyKmmnf99PZUog1A,1166
2
2
  injection/__init__.pyi,sha256=rOl1kA9BkIeXDr7OdBMifAg9xWwvme447yjDZooir18,10398
3
3
  injection/exceptions.py,sha256=v57yMujiq6H_zwwn30A8UYEZX9R9k-bY8FnsdaimPM4,1025
4
4
  injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- injection/utils.py,sha256=EuHMrix6gx2YnnUAn2_BPsDkvucGS5-pFhM3596oBK4,2796
5
+ injection/utils.py,sha256=bLIVA_3N3mTEQ3kGV4YzrWEnokHxUGqWYNKPggOOnpg,4065
6
6
  injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
8
8
  injection/_core/injectables.py,sha256=idNkQZZ29vd73G_lE-eS5C7zGeVe_ALNkUt8M6YjZrk,5519
@@ -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.6.post2.dist-info/METADATA,sha256=ST2y_3loRYXo9ugZ7yMrLNCBNMCaUTRdVenvDib8Dxc,3205
24
- python_injection-0.14.6.post2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- python_injection-0.14.6.post2.dist-info/RECORD,,
23
+ python_injection-0.15.0.dist-info/METADATA,sha256=LA4sTrPDIUAk-dwOH1zKj1Rjk-feJwq1r2eYHpLIJTw,3199
24
+ python_injection-0.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ python_injection-0.15.0.dist-info/RECORD,,