aspyx 1.4.1__tar.gz → 1.5.1__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.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- aspyx-1.5.1/.gitignore +194 -0
- {aspyx-1.4.1/src/aspyx.egg-info → aspyx-1.5.1}/PKG-INFO +7 -13
- {aspyx-1.4.1 → aspyx-1.5.1}/README.md +3 -7
- aspyx-1.5.1/pyproject.toml +22 -0
- aspyx-1.5.1/src/aspyx/__init__.py +1 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/__init__.py +2 -2
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/di.py +105 -80
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/exception/exception_manager.py +1 -1
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/reflection/proxy.py +33 -6
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/reflection/reflection.py +31 -7
- aspyx-1.5.1/tests/config.yaml +4 -0
- aspyx-1.5.1/tests/config1.yaml +4 -0
- aspyx-1.5.1/tests/di_import.py +14 -0
- aspyx-1.5.1/tests/sub_import.py +14 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_configuration.py +2 -2
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_cycle.py +11 -1
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_di.py +14 -4
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_exception_manager.py +2 -2
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_proxy.py +13 -0
- aspyx-1.4.1/PKG-INFO +0 -845
- aspyx-1.4.1/pyproject.toml +0 -24
- aspyx-1.4.1/setup.cfg +0 -4
- aspyx-1.4.1/src/aspyx/__init__.py +0 -0
- aspyx-1.4.1/src/aspyx.egg-info/SOURCES.txt +0 -35
- aspyx-1.4.1/src/aspyx.egg-info/dependency_links.txt +0 -1
- aspyx-1.4.1/src/aspyx.egg-info/requires.txt +0 -3
- aspyx-1.4.1/src/aspyx.egg-info/top_level.txt +0 -1
- {aspyx-1.4.1 → aspyx-1.5.1}/LICENSE +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/aop/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/aop/aop.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/configuration/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/configuration/configuration.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/threading/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/di/threading/synchronized.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/exception/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/threading/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/threading/thread_local.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/util/__init__.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/src/aspyx/util/stringbuilder.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_aop.py +0 -0
- {aspyx-1.4.1 → aspyx-1.5.1}/tests/test_reflection.py +0 -0
aspyx-1.5.1/.gitignore
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# Abstra
|
|
171
|
+
# Abstra is an AI-powered process automation framework.
|
|
172
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
173
|
+
# Learn more at https://abstra.io/docs
|
|
174
|
+
.abstra/
|
|
175
|
+
|
|
176
|
+
# Visual Studio Code
|
|
177
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
178
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
179
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
180
|
+
# you could uncomment the following to ignore the enitre vscode folder
|
|
181
|
+
# .vscode/
|
|
182
|
+
|
|
183
|
+
# Ruff stuff:
|
|
184
|
+
.ruff_cache/
|
|
185
|
+
|
|
186
|
+
# PyPI configuration file
|
|
187
|
+
.pypirc
|
|
188
|
+
|
|
189
|
+
# Cursor
|
|
190
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
191
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
192
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
193
|
+
.cursorignore
|
|
194
|
+
.cursorindexingignore
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: A DI and AOP library for Python
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -24,13 +24,11 @@ License: MIT License
|
|
|
24
24
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
|
-
|
|
27
|
+
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: python-dotenv~=1.1.0
|
|
30
|
+
Requires-Dist: pyyaml~=6.0.2
|
|
29
31
|
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: mkdocstrings-python; extra == "dev"
|
|
33
|
-
Dynamic: license-file
|
|
34
32
|
|
|
35
33
|
# aspyx
|
|
36
34
|
|
|
@@ -73,7 +71,7 @@ Dynamic: license-file
|
|
|
73
71
|
|
|
74
72
|
While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
|
|
75
73
|
|
|
76
|
-
- bring both di and AOP features together in a lightweight library
|
|
74
|
+
- bring both di and AOP features together in a lightweight library,
|
|
77
75
|
- be as minimal invasive as possible,
|
|
78
76
|
- offering mechanisms to easily extend and customize features without touching the core,
|
|
79
77
|
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers and only requires a minimum initial learning curve
|
|
@@ -85,7 +83,7 @@ The AOP integration, in particular, makes a lot of sense because:
|
|
|
85
83
|
|
|
86
84
|
# Overview
|
|
87
85
|
|
|
88
|
-
Aspyx is a lightweight - still only about
|
|
86
|
+
Aspyx is a lightweight - still only about 2K LOC - Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
89
87
|
|
|
90
88
|
The following DI features are supported
|
|
91
89
|
- constructor and setter injection
|
|
@@ -100,7 +98,7 @@ The following DI features are supported
|
|
|
100
98
|
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
101
99
|
- Automatic discovery and bundling of injectable objects based on their module location, including support for recursive imports
|
|
102
100
|
- Instantiation of one or possible more isolated container instances — called environments — each managing the lifecycle of a related set of objects,
|
|
103
|
-
|
|
101
|
+
- Support for hierarchical environments, enabling structured scoping and layered object management.
|
|
104
102
|
|
|
105
103
|
With respect to AOP:
|
|
106
104
|
- support for before, around, after and error aspects
|
|
@@ -839,7 +837,3 @@ class ExceptionAdvice:
|
|
|
839
837
|
**1.4.1**
|
|
840
838
|
|
|
841
839
|
- mkdocs
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
|
|
41
41
|
|
|
42
|
-
- bring both di and AOP features together in a lightweight library
|
|
42
|
+
- bring both di and AOP features together in a lightweight library,
|
|
43
43
|
- be as minimal invasive as possible,
|
|
44
44
|
- offering mechanisms to easily extend and customize features without touching the core,
|
|
45
45
|
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers and only requires a minimum initial learning curve
|
|
@@ -51,7 +51,7 @@ The AOP integration, in particular, makes a lot of sense because:
|
|
|
51
51
|
|
|
52
52
|
# Overview
|
|
53
53
|
|
|
54
|
-
Aspyx is a lightweight - still only about
|
|
54
|
+
Aspyx is a lightweight - still only about 2K LOC - Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
55
55
|
|
|
56
56
|
The following DI features are supported
|
|
57
57
|
- constructor and setter injection
|
|
@@ -66,7 +66,7 @@ The following DI features are supported
|
|
|
66
66
|
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
67
67
|
- Automatic discovery and bundling of injectable objects based on their module location, including support for recursive imports
|
|
68
68
|
- Instantiation of one or possible more isolated container instances — called environments — each managing the lifecycle of a related set of objects,
|
|
69
|
-
|
|
69
|
+
- Support for hierarchical environments, enabling structured scoping and layered object management.
|
|
70
70
|
|
|
71
71
|
With respect to AOP:
|
|
72
72
|
- support for before, around, after and error aspects
|
|
@@ -805,7 +805,3 @@ class ExceptionAdvice:
|
|
|
805
805
|
**1.4.1**
|
|
806
806
|
|
|
807
807
|
- mkdocs
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aspyx"
|
|
3
|
+
version = "1.5.1"
|
|
4
|
+
description = "A DI and AOP library for Python"
|
|
5
|
+
authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = { file = "LICENSE" }
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"python-dotenv~=1.1.0",
|
|
11
|
+
"pyyaml~=6.0.2"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["hatchling"]
|
|
16
|
+
build-backend = "hatchling.build"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build]
|
|
19
|
+
source = "src"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/aspyx"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
3
|
"""
|
|
4
|
-
from .di import conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, module, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
4
|
+
from .di import InstanceProvider, conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, module, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
5
5
|
|
|
6
6
|
# import something from the subpackages, so that the decorators are executed
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ __all__ = [
|
|
|
21
21
|
"inject",
|
|
22
22
|
"create",
|
|
23
23
|
"order",
|
|
24
|
-
|
|
24
|
+
"InstanceProvider",
|
|
25
25
|
"on_init",
|
|
26
26
|
"on_running",
|
|
27
27
|
"on_destroy",
|
|
@@ -316,30 +316,44 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
316
316
|
|
|
317
317
|
self.environment = environment
|
|
318
318
|
self.provider = provider
|
|
319
|
-
self.dependencies
|
|
319
|
+
self.dependencies : Optional[list[AbstractInstanceProvider]] = None # FOO
|
|
320
320
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
321
321
|
|
|
322
|
+
# public
|
|
323
|
+
|
|
324
|
+
def print_tree(self, prefix=""):
|
|
325
|
+
children = self.dependencies
|
|
326
|
+
last_index = len(children) - 1
|
|
327
|
+
print(prefix + "+- " + self.report())
|
|
328
|
+
|
|
329
|
+
for i, child in enumerate(children):
|
|
330
|
+
if i == last_index:
|
|
331
|
+
# Last child
|
|
332
|
+
child_prefix = prefix + " "
|
|
333
|
+
else:
|
|
334
|
+
# Not last child
|
|
335
|
+
child_prefix = prefix + "| "
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
cast(EnvironmentInstanceProvider, child).print_tree(child_prefix)
|
|
339
|
+
|
|
322
340
|
# implement
|
|
323
341
|
|
|
324
342
|
def resolve(self, context: Providers.ResolveContext):
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
343
|
+
if self.dependencies is None:
|
|
344
|
+
self.dependencies = []
|
|
345
|
+
context.push(self)
|
|
346
|
+
try:
|
|
347
|
+
type_and_params = self.provider.get_dependencies()
|
|
348
|
+
#params = type_and_params[1]
|
|
349
|
+
for type in type_and_params[0]:
|
|
350
|
+
provider = context.require_provider(type)
|
|
329
351
|
|
|
330
|
-
|
|
331
|
-
params = type_and_params[1]
|
|
332
|
-
for type in type_and_params[0]:
|
|
333
|
-
if params > 0:
|
|
334
|
-
params -= 1
|
|
335
|
-
self.dependencies.append(context.get_provider(type))
|
|
352
|
+
self.dependencies.append(provider)
|
|
336
353
|
|
|
337
|
-
provider = context.add_provider_dependency(self, type)
|
|
338
|
-
if provider is not None:
|
|
339
354
|
provider.resolve(context)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
context.add(*context.get_provider_dependencies(self))
|
|
355
|
+
finally:
|
|
356
|
+
context.pop()
|
|
343
357
|
|
|
344
358
|
def get_module(self) -> str:
|
|
345
359
|
return self.provider.get_module()
|
|
@@ -403,7 +417,11 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
403
417
|
for method in TypeDescriptor.for_type(self.type).get_methods():
|
|
404
418
|
if method.has_decorator(inject):
|
|
405
419
|
for param in method.param_types:
|
|
420
|
+
if not Providers.is_registered(param):
|
|
421
|
+
raise DIRegistrationException(f"{self.type.__name__}.{method.method.__name__} declares an unknown parameter type {param.__name__}")
|
|
422
|
+
|
|
406
423
|
types.append(param)
|
|
424
|
+
# done
|
|
407
425
|
|
|
408
426
|
return types, self.params
|
|
409
427
|
|
|
@@ -431,28 +449,28 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
431
449
|
|
|
432
450
|
# constructor
|
|
433
451
|
|
|
434
|
-
def __init__(self, clazz : Type, method
|
|
435
|
-
super().__init__(clazz, return_type, eager, scope)
|
|
452
|
+
def __init__(self, clazz : Type, method: TypeDescriptor.MethodDescriptor, eager = True, scope = "singleton"):
|
|
453
|
+
super().__init__(clazz, method.return_type, eager, scope)
|
|
436
454
|
|
|
437
|
-
self.method = method
|
|
455
|
+
self.method : TypeDescriptor.MethodDescriptor = method
|
|
438
456
|
|
|
439
457
|
# implement
|
|
440
458
|
|
|
441
459
|
def get_dependencies(self) -> (list[Type],int):
|
|
442
|
-
return [self.host], 1
|
|
460
|
+
return [self.host, *self.method.param_types], 1 + len(self.method.param_types)
|
|
443
461
|
|
|
444
462
|
def create(self, environment: Environment, *args):
|
|
445
463
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
446
464
|
|
|
447
|
-
instance = self.method(*args) # args[0]=self
|
|
465
|
+
instance = self.method.method(*args) # args[0]=self
|
|
448
466
|
|
|
449
467
|
return environment.created(instance)
|
|
450
468
|
|
|
451
469
|
def report(self) -> str:
|
|
452
|
-
return f"{self.host.__name__}.{self.method.__name__}"
|
|
470
|
+
return f"{self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__qualname__}"
|
|
453
471
|
|
|
454
472
|
def __str__(self):
|
|
455
|
-
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
473
|
+
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__name__})"
|
|
456
474
|
|
|
457
475
|
class FactoryInstanceProvider(InstanceProvider):
|
|
458
476
|
"""
|
|
@@ -483,7 +501,7 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
483
501
|
return environment.created(args[0].create())
|
|
484
502
|
|
|
485
503
|
def report(self) -> str:
|
|
486
|
-
return f"{self.host.__name__}.create"
|
|
504
|
+
return f"{self.host.__name__}.create() -> {self.type.__name__} "
|
|
487
505
|
|
|
488
506
|
def __str__(self):
|
|
489
507
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
@@ -545,69 +563,45 @@ class PostProcessor(LifecycleProcessor):
|
|
|
545
563
|
|
|
546
564
|
class Providers:
|
|
547
565
|
"""
|
|
548
|
-
The Providers class is a static class
|
|
566
|
+
The Providers class is a static class used in the context of the registration and resolution of InstanceProviders.
|
|
549
567
|
"""
|
|
550
568
|
# local class
|
|
551
569
|
|
|
552
570
|
class ResolveContext:
|
|
553
571
|
__slots__ = [
|
|
554
|
-
"dependencies",
|
|
555
572
|
"providers",
|
|
556
|
-
"
|
|
573
|
+
"path"
|
|
557
574
|
]
|
|
558
575
|
|
|
559
576
|
# constructor
|
|
560
577
|
|
|
561
578
|
def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
|
|
562
|
-
self.dependencies : list[EnvironmentInstanceProvider] = []
|
|
563
579
|
self.providers = providers
|
|
564
|
-
self.
|
|
580
|
+
self.path = []
|
|
565
581
|
|
|
566
582
|
# public
|
|
567
583
|
|
|
568
|
-
def
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
|
|
572
|
-
return self.provider_dependencies[provider]
|
|
573
|
-
|
|
574
|
-
def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
|
|
575
|
-
provider_dependencies = self.provider_dependencies.get(provider, None)
|
|
576
|
-
if provider_dependencies is None:
|
|
577
|
-
provider_dependencies = []
|
|
578
|
-
self.provider_dependencies[provider] = provider_dependencies
|
|
584
|
+
def push(self, provider):
|
|
585
|
+
self.path.append(provider)
|
|
579
586
|
|
|
580
|
-
|
|
587
|
+
def pop(self):
|
|
588
|
+
self.path.pop()
|
|
581
589
|
|
|
582
|
-
|
|
583
|
-
return None
|
|
584
|
-
|
|
585
|
-
provider_dependencies.append(provider)
|
|
586
|
-
|
|
587
|
-
return provider
|
|
588
|
-
|
|
589
|
-
def next(self):
|
|
590
|
-
self.dependencies.clear()
|
|
591
|
-
|
|
592
|
-
def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
590
|
+
def require_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
593
591
|
provider = self.providers.get(type, None)
|
|
594
592
|
if provider is None:
|
|
595
593
|
raise DIRegistrationException(f"Provider for {type} is not defined")
|
|
596
594
|
|
|
597
|
-
|
|
595
|
+
if provider in self.path:
|
|
596
|
+
raise DIRegistrationException(self.cycle_report(provider))
|
|
598
597
|
|
|
599
|
-
|
|
600
|
-
for provider in providers:
|
|
601
|
-
if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
|
|
602
|
-
raise DIRegistrationException(self.cycle_report(provider))
|
|
603
|
-
|
|
604
|
-
self.dependencies.append(provider)
|
|
598
|
+
return provider
|
|
605
599
|
|
|
606
600
|
def cycle_report(self, provider: AbstractInstanceProvider):
|
|
607
601
|
cycle = ""
|
|
608
602
|
|
|
609
603
|
first = True
|
|
610
|
-
for p in self.
|
|
604
|
+
for p in self.path:
|
|
611
605
|
if not first:
|
|
612
606
|
cycle += " -> "
|
|
613
607
|
|
|
@@ -638,6 +632,10 @@ class Providers:
|
|
|
638
632
|
else:
|
|
639
633
|
candidates.append(provider)
|
|
640
634
|
|
|
635
|
+
@classmethod
|
|
636
|
+
def is_registered(cls,type: Type) -> bool:
|
|
637
|
+
return Providers.providers.get(type, None) is not None
|
|
638
|
+
|
|
641
639
|
# add factories lazily
|
|
642
640
|
|
|
643
641
|
@classmethod
|
|
@@ -652,7 +650,7 @@ class Providers:
|
|
|
652
650
|
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
653
651
|
|
|
654
652
|
context: ConditionContext = {
|
|
655
|
-
"requires_feature":
|
|
653
|
+
"requires_feature": environment.has_feature,
|
|
656
654
|
"requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
|
|
657
655
|
}
|
|
658
656
|
|
|
@@ -708,10 +706,13 @@ class Providers:
|
|
|
708
706
|
if type is provider.get_type():
|
|
709
707
|
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
710
708
|
|
|
711
|
-
if
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
709
|
+
if existing_provider.get_type() is not type:
|
|
710
|
+
# only overwrite if the existing provider is not specific
|
|
711
|
+
|
|
712
|
+
if isinstance(existing_provider, AmbiguousProvider):
|
|
713
|
+
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
714
|
+
else:
|
|
715
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
715
716
|
|
|
716
717
|
# recursion
|
|
717
718
|
|
|
@@ -747,7 +748,6 @@ class Providers:
|
|
|
747
748
|
provider_context = Providers.ResolveContext(providers)
|
|
748
749
|
for provider in mapped.values():
|
|
749
750
|
provider.resolve(provider_context)
|
|
750
|
-
provider_context.next() # clear dependencies
|
|
751
751
|
|
|
752
752
|
# done
|
|
753
753
|
|
|
@@ -763,8 +763,7 @@ def register_factories(cls: Type):
|
|
|
763
763
|
if return_type is None:
|
|
764
764
|
raise DIRegistrationException(f"{cls.__name__}.{method.method.__name__} expected to have a return type")
|
|
765
765
|
|
|
766
|
-
Providers.register(FunctionInstanceProvider(cls, method
|
|
767
|
-
create_decorator.args[1]))
|
|
766
|
+
Providers.register(FunctionInstanceProvider(cls, method, create_decorator.args[0], create_decorator.args[1]))
|
|
768
767
|
def order(prio = 0):
|
|
769
768
|
def decorator(cls):
|
|
770
769
|
Decorators.add(cls, order, prio)
|
|
@@ -939,12 +938,12 @@ class Environment:
|
|
|
939
938
|
class Foo:
|
|
940
939
|
def __init__(self):
|
|
941
940
|
|
|
942
|
-
@
|
|
943
|
-
class
|
|
941
|
+
@module()
|
|
942
|
+
class Module:
|
|
944
943
|
def __init__(self):
|
|
945
944
|
pass
|
|
946
945
|
|
|
947
|
-
environment = Environment(
|
|
946
|
+
environment = Environment(Module)
|
|
948
947
|
|
|
949
948
|
foo = environment.get(Foo) # will create an instance of Foo
|
|
950
949
|
```
|
|
@@ -976,6 +975,11 @@ class Environment:
|
|
|
976
975
|
parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
|
|
977
976
|
"""
|
|
978
977
|
|
|
978
|
+
def add_provider(type: Type, provider: AbstractInstanceProvider):
|
|
979
|
+
Environment.logger.debug("\tadd provider %s for %s", provider, type)
|
|
980
|
+
|
|
981
|
+
self.providers[type] = provider
|
|
982
|
+
|
|
979
983
|
Environment.logger.debug("create environment for class %s", env.__qualname__)
|
|
980
984
|
|
|
981
985
|
# initialize
|
|
@@ -996,9 +1000,11 @@ class Environment:
|
|
|
996
1000
|
for provider_type, inherited_provider in self.parent.providers.items():
|
|
997
1001
|
if inherited_provider.get_scope() == "environment":
|
|
998
1002
|
# replace with own environment instance provider
|
|
999
|
-
|
|
1003
|
+
provider = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
|
|
1004
|
+
provider.dependencies = [] # ??
|
|
1005
|
+
add_provider(provider_type, provider)
|
|
1000
1006
|
else:
|
|
1001
|
-
|
|
1007
|
+
add_provider(provider_type, inherited_provider)
|
|
1002
1008
|
|
|
1003
1009
|
# inherit processors as is unless they have an environment scope
|
|
1004
1010
|
|
|
@@ -1019,16 +1025,35 @@ class Environment:
|
|
|
1019
1025
|
|
|
1020
1026
|
loaded = set()
|
|
1021
1027
|
|
|
1022
|
-
def add_provider(type: Type, provider: AbstractInstanceProvider):
|
|
1023
|
-
Environment.logger.debug("\tadd provider %s for %s", provider, type)
|
|
1024
|
-
|
|
1025
|
-
self.providers[type] = provider
|
|
1026
|
-
|
|
1027
1028
|
def get_type_package(type: Type):
|
|
1028
1029
|
module_name = type.__module__
|
|
1029
|
-
module = sys.modules
|
|
1030
|
+
module = sys.modules.get(module_name)
|
|
1031
|
+
|
|
1032
|
+
if not module:
|
|
1033
|
+
raise ImportError(f"Module {module_name} not found")
|
|
1034
|
+
|
|
1035
|
+
# Try to get the package
|
|
1036
|
+
|
|
1037
|
+
package = getattr(module, '__package__', None)
|
|
1038
|
+
|
|
1039
|
+
# Fallback: if module is __main__, try to infer from the module name if possible
|
|
1040
|
+
|
|
1041
|
+
if not package:
|
|
1042
|
+
if module_name == '__main__':
|
|
1043
|
+
# Try to resolve real name via __file__
|
|
1044
|
+
path = getattr(module, '__file__', None)
|
|
1045
|
+
if path:
|
|
1046
|
+
Environment.logger.warning(
|
|
1047
|
+
"Module is __main__; consider running via -m to preserve package context")
|
|
1048
|
+
return ''
|
|
1049
|
+
|
|
1050
|
+
# Try to infer package name from module name
|
|
1051
|
+
|
|
1052
|
+
parts = module_name.split('.')
|
|
1053
|
+
if len(parts) > 1:
|
|
1054
|
+
return '.'.join(parts[:-1])
|
|
1030
1055
|
|
|
1031
|
-
return
|
|
1056
|
+
return package or ''
|
|
1032
1057
|
|
|
1033
1058
|
def import_package(name: str):
|
|
1034
1059
|
"""Import a package and all its submodules recursively."""
|