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.
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/PKG-INFO +1 -1
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/invertible.py +2 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/module.py +2 -2
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/scope.py +3 -3
- python_injection-0.15.0/injection/utils.py +130 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/pyproject.toml +1 -1
- python_injection-0.14.6.post1/injection/utils.py +0 -96
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/.gitignore +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/README.md +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/__init__.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/__init__.pyi +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/__init__.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/asynchronous.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/event.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/key.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/type.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/descriptors.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/injectables.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/slots.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/exceptions.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/integrations/__init__.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/integrations/fastapi.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/py.typed +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/testing/__init__.py +0 -0
- {python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/testing/__init__.pyi +0 -0
@@ -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
|
-
|
160
|
+
states = __SHARED_SCOPES
|
161
161
|
|
162
162
|
else:
|
163
163
|
is_already_defined = bool(get_scope(name, default=None))
|
164
|
-
|
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
|
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)
|
@@ -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)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/__init__.py
RENAMED
File without changes
|
{python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/_core/common/asynchronous.py
RENAMED
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
|
{python_injection-0.14.6.post1 → python_injection-0.15.0}/injection/integrations/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|