module-dependency 1.1.4__tar.gz → 1.1.6__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.
- {module_dependency-1.1.4 → module_dependency-1.1.6}/CHANGELOG.md +7 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/PKG-INFO +12 -14
- module_dependency-1.1.6/README.md +251 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6/docs}/README.md +3 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/mkdocs.yaml +1 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/pyproject.toml +8 -5
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/entrypoint.py +6 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/injectable.py +16 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/utils/cycle.py +1 -1
- module_dependency-1.1.6/src/dependency/library/graph/__init__.py +5 -0
- module_dependency-1.1.6/src/dependency/library/graph/generate.py +56 -0
- module_dependency-1.1.6/src/dependency/library/graph/models.py +79 -0
- {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/observer.py +25 -22
- {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/state.py +2 -2
- module_dependency-1.1.6/src/dependency/library/threading.py +44 -0
- module_dependency-1.1.6/src/example/diagram.svg +143 -0
- module_dependency-1.1.6/src/example/plugin/base/deferred/__init__.py +81 -0
- module_dependency-1.1.6/src/example/plugin/base/deferred/interfaces.py +14 -0
- module_dependency-1.1.6/src/example/plugin/base/deferred/uvloop.py +47 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/imports.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/events.py +1 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productA.py +3 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productB.py +3 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productC.py +2 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/observer/__init__.py +2 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/observer/publisherA.py +9 -4
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/factory/productA.py +6 -3
- module_dependency-1.1.6/src/graph.py +6 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/entrypoint.pyi +6 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/injectable.pyi +10 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/mixin.pyi +0 -1
- module_dependency-1.1.6/stubs/dependency/library/graph/__init__.pyi +3 -0
- module_dependency-1.1.6/stubs/dependency/library/graph/generate.pyi +14 -0
- module_dependency-1.1.6/stubs/dependency/library/graph/models.pyi +32 -0
- module_dependency-1.1.6/stubs/dependency/library/graph/utils.pyi +3 -0
- module_dependency-1.1.6/stubs/dependency/library/patterns/composite.pyi +10 -0
- module_dependency-1.1.6/stubs/dependency/library/patterns/observer.pyi +15 -0
- module_dependency-1.1.6/stubs/dependency/library/patterns/state.pyi +10 -0
- module_dependency-1.1.6/stubs/dependency/library/threading.pyi +9 -0
- module_dependency-1.1.6/tests/core/test_agrupation.py +49 -0
- module_dependency-1.1.6/tests/core/test_declaration.py +87 -0
- module_dependency-1.1.6/tests/core/test_detectcycle.py +65 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_exceptions.py +11 -1
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_injection.py +30 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_interfaces.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_products.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_providers.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_resolution.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_resource.py +1 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_validation.py +14 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/example/test_component.py +2 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/example/test_module.py +2 -0
- module_dependency-1.1.4/docs/README.md +0 -2
- module_dependency-1.1.4/src/library/patterns/decorator.py +0 -7
- module_dependency-1.1.4/tests/core/test_agrupation.py +0 -21
- module_dependency-1.1.4/tests/core/test_declaration.py +0 -39
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/docs.yaml +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/release.yml +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/testing.yml +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.gitignore +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.mypy.ini +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/.vscode/settings.json +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/LICENSE +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/ARCHITECTURE.md +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/REFERENCES.md +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/reference/cli.md +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/reference/core.md +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/requirements.txt +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/base.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/component.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/instance.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/module.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/plugin.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/models/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/models/base.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/component.py.j2 +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/instance.py.j2 +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/module.py.j2 +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/plugin.py.j2 +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/fallback.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/module.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/plugin.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/component.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/instance.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/product.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/validation.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/exceptions.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/injection.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/mixin.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/wiring.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/container.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/errors.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/registry.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/resolver.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/strategy.py +0 -0
- {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/__init__.py +0 -0
- {module_dependency-1.1.4/src → module_dependency-1.1.6/src/dependency}/library/patterns/composite.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/imports.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/plugins.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/config.json +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/concrete1.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/concrete2.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/concreteA.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/concreteB.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/concreteA.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/concreteB.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/abstraction/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/abstraction/concrete.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/implementation/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/implementation/concrete.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/number/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/number/fake.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/settings.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/string/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/string/fake.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/bridge/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/bridge/bridgeA.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/providers/creatorA.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/providers/creatorB.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/imports.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/interfaces.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/settings.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/facade/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/facade/facadeA.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/factory/__init__.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/imports.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/interfaces.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/settings.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/src/main.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/base.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/component.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/instance.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/module.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/plugin.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/models/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/models/base.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/fallback.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/module.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/plugin.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/component.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/instance.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/product.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/validation.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/exceptions.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/injection.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/wiring.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/container.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/errors.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/registry.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/resolver.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/strategy.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/utils/cycle.pyi +0 -0
- /module_dependency-1.1.4/src/library/patterns/__init__.py → /module_dependency-1.1.6/stubs/dependency/library/patterns/__init__.pyi +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/cli/test_generation.py +0 -0
- {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/example/test_application.py +0 -0
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [v1.1.5] - 2026-03-
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Extensive unit tests for core modules, including resolution strategy, injection, and product management
|
|
13
|
+
- Updated documentation with examples and usage guidelines for new features and changes
|
|
14
|
+
|
|
8
15
|
## [v1.1.4] - 2026-03-19
|
|
9
16
|
|
|
10
17
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: module_dependency
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.6
|
|
4
4
|
Summary: A dependency management tool for Python projects.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fabaindaiz/module-injection
|
|
6
6
|
Project-URL: Documentation, https://github.com/fabaindaiz/module-dependency/tree/main/docs
|
|
@@ -15,14 +15,14 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
19
|
Requires-Dist: dependency-injector
|
|
20
20
|
Requires-Dist: jinja2
|
|
21
21
|
Requires-Dist: pydantic
|
|
22
22
|
Requires-Dist: pydantic-settings
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# Module Dependency
|
|
26
26
|
|
|
27
27
|
A Dependency Injection Framework for Modular Embedded Python Applications.
|
|
28
28
|
|
|
@@ -42,6 +42,8 @@ This project is available on PyPI on [module_dependency](https://pypi.org/projec
|
|
|
42
42
|
pip install module-dependency
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
Documentation are available on [GitHub Pages](https://fabaindaiz.github.io/module-dependency/).
|
|
46
|
+
|
|
45
47
|
## Core Components
|
|
46
48
|
|
|
47
49
|
The project is built around three components that implement different aspects of dependency management:
|
|
@@ -210,7 +212,7 @@ from ...plugin.....other_product import OtherProduct
|
|
|
210
212
|
@product(
|
|
211
213
|
module=SomeModule, # Declares the module or plugin this component belongs to
|
|
212
214
|
imports=[SomeService, ...], # List of dependencies (components) that this product needs
|
|
213
|
-
provider=providers.
|
|
215
|
+
provider=providers.Factory, # Provider type (Singleton, Factory, Resource)
|
|
214
216
|
)
|
|
215
217
|
class SomeProduct(Interface, Product):
|
|
216
218
|
"""This is the product class. This class will check for its dependencies.
|
|
@@ -238,32 +240,28 @@ class SomeProduct(Interface, Product):
|
|
|
238
240
|
|
|
239
241
|
## Important Notes
|
|
240
242
|
|
|
241
|
-
-
|
|
243
|
+
- Remember to declare all the dependencies you need in the `imports` parameter of the `@instance` or `@product` decorator.
|
|
242
244
|
- Read the documentation carefully and refer to the examples to understand the framework's behavior.
|
|
243
245
|
|
|
244
246
|
## Usage Examples
|
|
245
247
|
|
|
246
248
|
This repository includes a practical example demonstrating how to use the framework. You can find this example in the `example` directory. It showcases the implementation of the core components and how they interact to manage dependencies effectively in a sample application.
|
|
247
249
|
|
|
248
|
-
This example requires the `module-injection` package to be installed and the `library` folder to be present in the project root.
|
|
249
|
-
|
|
250
250
|
## Future Work
|
|
251
251
|
|
|
252
252
|
This project is a work in progress, and there are several improvements and enhancements planned for the future.
|
|
253
253
|
|
|
254
254
|
Some planned features are:
|
|
255
|
+
- Add pre-defined components for common patterns and use cases
|
|
256
|
+
- Dependency CLI support for easier interaction with the framework
|
|
257
|
+
- Pytest testing framework integration for better test management
|
|
258
|
+
|
|
259
|
+
Some new improvements that has been recently added:
|
|
255
260
|
- Enhance documentation and examples for better understanding
|
|
256
261
|
- Implement framework API and extension points for customization
|
|
257
262
|
- Improve injection resolution and initialization process
|
|
258
|
-
- Testing framework integration for better test coverage
|
|
259
263
|
- Visualization tools for dependency graphs and relationships
|
|
260
264
|
|
|
261
|
-
Some of the areas that will be explored in the future include:
|
|
262
|
-
- Add some basic components and plugins for common use cases
|
|
263
|
-
- Dependency CLI support for easier interaction with the framework
|
|
264
|
-
- Explore more advanced dependency injection patterns and use cases
|
|
265
|
-
- Improve testing and validation for projects using this framework
|
|
266
|
-
|
|
267
265
|
Pending issues that eventually will be addressed:
|
|
268
266
|
- Migration guide from previous versions (some breaking changes were introduced)
|
|
269
267
|
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Module Dependency
|
|
2
|
+
|
|
3
|
+
A Dependency Injection Framework for Modular Embedded Python Applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The goal of this project is to provide a comprehensive framework for managing structure for complex Python applications. The framework is designed to be modular, allowing developers to define components, interfaces, and instances that can be easily managed and injected throughout the application.
|
|
8
|
+
|
|
9
|
+
Declare components with interfaces, provide multiple implementations of them, and manage which implementation to use at runtime. Multiple components can be organized and composed together to form complex behaviors using modular design principles.
|
|
10
|
+
|
|
11
|
+
This repository includes a working example of a simple application that demonstrates these concepts in action. Based on a real-world use case, the example showcases how to effectively manage dependencies and implement modular design patterns in an embedded Python environment.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
This project is available on PyPI on [module_dependency](https://pypi.org/project/module_dependency/). It can be installed using pip:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install module-dependency
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Documentation are available on [GitHub Pages](https://fabaindaiz.github.io/module-dependency/).
|
|
22
|
+
|
|
23
|
+
## Core Components
|
|
24
|
+
|
|
25
|
+
The project is built around three components that implement different aspects of dependency management:
|
|
26
|
+
|
|
27
|
+
### 1. Module
|
|
28
|
+
- Acts as a container for organizing and grouping related dependencies
|
|
29
|
+
- Facilitates modular design and hierarchical structuring of application components
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from dependency.core import Module, module
|
|
33
|
+
from ...plugin.........module import ParentModule
|
|
34
|
+
|
|
35
|
+
@module(
|
|
36
|
+
module=ParentModule, # Declares the parent module (leave empty for plugins)
|
|
37
|
+
)
|
|
38
|
+
class SomeModule(Module):
|
|
39
|
+
"""This is a module class. Use this to group related components.
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Component
|
|
45
|
+
- Defines abstract interfaces or contracts for dependencies
|
|
46
|
+
- Promotes loose coupling and enables easier testing and maintenance
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from abc import ABC, abstractmethod
|
|
50
|
+
from dependency.core import Component, component
|
|
51
|
+
from ...plugin.........module import SomeModule
|
|
52
|
+
|
|
53
|
+
@component(
|
|
54
|
+
module=SomeModule, # Declares the module or plugin this component belongs to
|
|
55
|
+
)
|
|
56
|
+
class SomeService(ABC, Component):
|
|
57
|
+
"""This is the component class. A instance will be injected here.
|
|
58
|
+
Components are only started when provided or bootstrapped.
|
|
59
|
+
Components also defines the interface for all instances.
|
|
60
|
+
"""
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def method(self, ...) -> ...:
|
|
63
|
+
pass
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Instance
|
|
67
|
+
- Delivers concrete implementations of Components
|
|
68
|
+
- Manages the lifecycle and injection of dependency objects
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from dependency_injector.wiring import inject
|
|
72
|
+
from dependency.core import instance, providers
|
|
73
|
+
from dependency.core.injection import LazyProvide
|
|
74
|
+
from ...plugin.........component import SomeService
|
|
75
|
+
from ...plugin...other_component import OtherService
|
|
76
|
+
from ...plugin...........product import SomeProduct
|
|
77
|
+
|
|
78
|
+
@instance(
|
|
79
|
+
imports=[OtherService, ...], # List of dependencies (components) that this product needs
|
|
80
|
+
provider=providers.Singleton, # Provider type from di (Singleton, Factory, Resource)
|
|
81
|
+
bootstrap=False, # Whether to bootstrap on application start
|
|
82
|
+
)
|
|
83
|
+
class ImplementedSomeService(SomeService):
|
|
84
|
+
"""This is a instance class. Here the component is implemented.
|
|
85
|
+
Instances are injected into the respective components when provided.
|
|
86
|
+
Instances must inherit from the component class and implement all its methods.
|
|
87
|
+
"""
|
|
88
|
+
def __init__(self) -> None:
|
|
89
|
+
"""Init method will be called when the instance is started.
|
|
90
|
+
This will happen once for singleton and every time for factories.
|
|
91
|
+
"""
|
|
92
|
+
# Once declared, i can use the dependencies for the class
|
|
93
|
+
self.dependency: OtherService = OtherService.provide()
|
|
94
|
+
|
|
95
|
+
@inject
|
|
96
|
+
def method(self,
|
|
97
|
+
# Dependencies also can be provided using @inject decorator with LazyProvide
|
|
98
|
+
# With @inject always use LazyProvide, to avoid deferred evaluation issues.
|
|
99
|
+
dependency: OtherService = LazyProvide(OtherService.reference),
|
|
100
|
+
...) -> ...:
|
|
101
|
+
"""Methods declared in the interface must be implemented.
|
|
102
|
+
"""
|
|
103
|
+
# Once declared, i can safely create any product
|
|
104
|
+
# Products are just normal classes (see next section)
|
|
105
|
+
product = SomeProduct()
|
|
106
|
+
|
|
107
|
+
# You can do anything here
|
|
108
|
+
do_something()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
These components work together to create a powerful and flexible dependency injection system, allowing for more maintainable and testable Python applications.
|
|
112
|
+
|
|
113
|
+
## Extra Components
|
|
114
|
+
|
|
115
|
+
The project has additional components that enhance its functionality and organization. These components include:
|
|
116
|
+
|
|
117
|
+
### 1. Entrypoint
|
|
118
|
+
- Represents a entrypoint for the application
|
|
119
|
+
- Responsible for initializing and starting the application
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from dependency.core import Entrypoint, Container
|
|
123
|
+
from ...plugin...... import SomePlugin
|
|
124
|
+
|
|
125
|
+
class SomeApplication(Entrypoint):
|
|
126
|
+
"""This is an application entry point.
|
|
127
|
+
Plugins included here will be loaded and initialized.
|
|
128
|
+
"""
|
|
129
|
+
def __init__(self) -> None:
|
|
130
|
+
# Declare all the plugins that will be used in the application
|
|
131
|
+
# Its recommended to declare the plugins list them in a separate file
|
|
132
|
+
# You can also include in the same file all the instances imports
|
|
133
|
+
PLUGINS = [
|
|
134
|
+
SomePlugin,
|
|
135
|
+
...
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# This is the main container, it will hold all the containers and providers
|
|
139
|
+
# Requires to have a valid configuration that will be used to initialize plugins
|
|
140
|
+
container = Container.from_dict(config={...}, required=True)
|
|
141
|
+
super().__init__(container, PLUGINS)
|
|
142
|
+
|
|
143
|
+
# Import all the instances that will be used on the application
|
|
144
|
+
# You can apply some logic to determine which instances to import
|
|
145
|
+
# This will automatically generate the internal provider structure
|
|
146
|
+
import ...plugin.........instance
|
|
147
|
+
|
|
148
|
+
# Once all the plugins and instances are imported, we can initialize the application
|
|
149
|
+
super().initialize()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 2. Plugin
|
|
153
|
+
- Represents a special module that can be included in the application
|
|
154
|
+
- Provides additional functionality and features to the application
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from pydantic import BaseModel
|
|
158
|
+
from dependency.core import Plugin, PluginMeta, module
|
|
159
|
+
|
|
160
|
+
class SomePluginConfig(BaseModel):
|
|
161
|
+
"""Include configuration options for the plugin.
|
|
162
|
+
"""
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
@module()
|
|
166
|
+
class SomePlugin(Plugin):
|
|
167
|
+
"""This is a plugin class. Plugins can be included in the application.
|
|
168
|
+
Plugins are modules that provide additional functionality.
|
|
169
|
+
"""
|
|
170
|
+
# Meta information about the plugin (only affects logging)
|
|
171
|
+
meta = PluginMeta(name="SomePlugin", version="0.0.1")
|
|
172
|
+
|
|
173
|
+
# Type hint for the plugin configuration
|
|
174
|
+
# On startup, config will be instantiated using the container config
|
|
175
|
+
config: SomePluginConfig
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 3. Product
|
|
179
|
+
- Represents a class that requires dependencies injected from the framework
|
|
180
|
+
- Allows to provide standalone classes without the need to define new providers
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from dependency.core import Product, product, providers
|
|
184
|
+
from dependency.core.injection import LazyProvide, inject
|
|
185
|
+
from ...plugin.........component import SomeService
|
|
186
|
+
from ...plugin.....other_product import OtherProduct
|
|
187
|
+
|
|
188
|
+
@product(
|
|
189
|
+
module=SomeModule, # Declares the module or plugin this component belongs to
|
|
190
|
+
imports=[SomeService, ...], # List of dependencies (components) that this product needs
|
|
191
|
+
provider=providers.Factory, # Provider type (Singleton, Factory, Resource)
|
|
192
|
+
)
|
|
193
|
+
class SomeProduct(Interface, Product):
|
|
194
|
+
"""This is the product class. This class will check for its dependencies.
|
|
195
|
+
Products must be declared in some instance and can be instantiated as normal classes.
|
|
196
|
+
"""
|
|
197
|
+
def __init__(self, ...) -> None:
|
|
198
|
+
# Dependencies can be used in the same way as before
|
|
199
|
+
self.dependency: SomeService = SomeService.provide()
|
|
200
|
+
|
|
201
|
+
@inject
|
|
202
|
+
def method(self,
|
|
203
|
+
# Dependencies also can be provided using @inject decorator with LazyProvide
|
|
204
|
+
# With @inject always use LazyProvide, to avoid deferred evaluation issues.
|
|
205
|
+
dependency: SomeService = LazyProvide(SomeService.reference),
|
|
206
|
+
...) -> ...:
|
|
207
|
+
"""Product interface can be defined using normal inheritance.
|
|
208
|
+
"""
|
|
209
|
+
# Once declared, i can safely create any sub-product
|
|
210
|
+
# Products are just normal classes (see next section)
|
|
211
|
+
product = OtherProduct()
|
|
212
|
+
|
|
213
|
+
# You can do anything here
|
|
214
|
+
do_something()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Important Notes
|
|
218
|
+
|
|
219
|
+
- Remember to declare all the dependencies you need in the `imports` parameter of the `@instance` or `@product` decorator.
|
|
220
|
+
- Read the documentation carefully and refer to the examples to understand the framework's behavior.
|
|
221
|
+
|
|
222
|
+
## Usage Examples
|
|
223
|
+
|
|
224
|
+
This repository includes a practical example demonstrating how to use the framework. You can find this example in the `example` directory. It showcases the implementation of the core components and how they interact to manage dependencies effectively in a sample application.
|
|
225
|
+
|
|
226
|
+
## Future Work
|
|
227
|
+
|
|
228
|
+
This project is a work in progress, and there are several improvements and enhancements planned for the future.
|
|
229
|
+
|
|
230
|
+
Some planned features are:
|
|
231
|
+
- Add pre-defined components for common patterns and use cases
|
|
232
|
+
- Dependency CLI support for easier interaction with the framework
|
|
233
|
+
- Pytest testing framework integration for better test management
|
|
234
|
+
|
|
235
|
+
Some new improvements that has been recently added:
|
|
236
|
+
- Enhance documentation and examples for better understanding
|
|
237
|
+
- Implement framework API and extension points for customization
|
|
238
|
+
- Improve injection resolution and initialization process
|
|
239
|
+
- Visualization tools for dependency graphs and relationships
|
|
240
|
+
|
|
241
|
+
Pending issues that eventually will be addressed:
|
|
242
|
+
- Migration guide from previous versions (some breaking changes were introduced)
|
|
243
|
+
|
|
244
|
+
## Aknowledgements
|
|
245
|
+
|
|
246
|
+
This project depends on:
|
|
247
|
+
- [dependency-injector](https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html) a robust and flexible framework for dependency injection in Python.
|
|
248
|
+
- [pydantic](https://docs.pydantic.dev/latest/) a data validation and settings management library using Python type annotations.
|
|
249
|
+
- [jinja2](https://jinja.palletsprojects.com/) a modern and designer-friendly templating engine for Python.
|
|
250
|
+
|
|
251
|
+
Thanks to [Reite](https://reite.cl/) for providing inspiration and guidance throughout the development of this project.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Module Dependency
|
|
2
2
|
|
|
3
3
|
A Dependency Injection Framework for Modular Embedded Python Applications.
|
|
4
4
|
|
|
@@ -18,6 +18,8 @@ This project is available on PyPI on [module_dependency](https://pypi.org/projec
|
|
|
18
18
|
pip install module-dependency
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Documentation are available on [GitHub Pages](https://fabaindaiz.github.io/module-dependency/).
|
|
22
|
+
|
|
21
23
|
## Core Components
|
|
22
24
|
|
|
23
25
|
The project is built around three components that implement different aspects of dependency management:
|
|
@@ -17,10 +17,12 @@ packages = [
|
|
|
17
17
|
template = "default"
|
|
18
18
|
skip-install = false
|
|
19
19
|
dependencies = [
|
|
20
|
+
"graphviz",
|
|
20
21
|
"mypy",
|
|
21
22
|
"pytest-asyncio",
|
|
22
23
|
"pytest-cov",
|
|
23
24
|
"pytest-xdist",
|
|
25
|
+
"uvloop",
|
|
24
26
|
]
|
|
25
27
|
|
|
26
28
|
[tool.hatch.envs.build.env-vars]
|
|
@@ -28,6 +30,7 @@ PYTHONPATH = "src"
|
|
|
28
30
|
|
|
29
31
|
[tool.hatch.envs.build.scripts]
|
|
30
32
|
coverage = "pytest --cov src/dependency --cov-report html -n auto --dist=loadfile"
|
|
33
|
+
graph = "python src/graph.py"
|
|
31
34
|
example = "python src/main.py"
|
|
32
35
|
stubs = "stubgen src/dependency -o stubs --include-docstrings"
|
|
33
36
|
tests = "pytest -n auto --dist=loadfile"
|
|
@@ -37,9 +40,9 @@ typecheck = "mypy --strict src/dependency"
|
|
|
37
40
|
template = "default"
|
|
38
41
|
skip-install = false
|
|
39
42
|
dependencies = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
"mkdocs",
|
|
44
|
+
"mkdocs-material",
|
|
45
|
+
"mkdocstrings[python]",
|
|
43
46
|
]
|
|
44
47
|
|
|
45
48
|
[tool.hatch.envs.docs.env-vars]
|
|
@@ -66,14 +69,14 @@ testpaths = ["tests"]
|
|
|
66
69
|
|
|
67
70
|
[project]
|
|
68
71
|
name = "module_dependency"
|
|
69
|
-
version = "1.1.
|
|
72
|
+
version = "1.1.6"
|
|
70
73
|
dependencies = [
|
|
71
74
|
"dependency_injector",
|
|
72
75
|
"jinja2",
|
|
73
76
|
"pydantic",
|
|
74
77
|
"pydantic-settings",
|
|
75
78
|
]
|
|
76
|
-
requires-python = ">=3.
|
|
79
|
+
requires-python = ">=3.11"
|
|
77
80
|
authors = [
|
|
78
81
|
{ name="Fabian D", email="github.clapping767@passmail.net" },
|
|
79
82
|
]
|
{module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/entrypoint.py
RENAMED
|
@@ -8,6 +8,7 @@ from dependency.core.resolution.container import Container
|
|
|
8
8
|
from dependency.core.agrupation.fallback import initialize_fallback
|
|
9
9
|
from dependency.core.resolution.resolver import InjectionResolver
|
|
10
10
|
from dependency.core.resolution.strategy import ResolutionStrategy
|
|
11
|
+
from dependency.library.threading import handle_exit
|
|
11
12
|
_logger = logging.getLogger("dependency.loader")
|
|
12
13
|
|
|
13
14
|
class Entrypoint:
|
|
@@ -69,6 +70,10 @@ class Entrypoint:
|
|
|
69
70
|
)
|
|
70
71
|
_logger.info(f"Application initialized in {time.time() - self.init_time} seconds")
|
|
71
72
|
|
|
73
|
+
@handle_exit
|
|
72
74
|
def main_loop(self) -> None:
|
|
73
|
-
"""Main loop for the application. Waits indefinitely.
|
|
75
|
+
"""Main loop for the application. Waits indefinitely.
|
|
76
|
+
|
|
77
|
+
This method is intended to be called after the application has been initialized.
|
|
78
|
+
"""
|
|
74
79
|
Event().wait() # pragma: no cover
|
{module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/injectable.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from typing import Any, Callable, Iterable, Optional
|
|
3
4
|
_logger = logging.getLogger("dependency.loader")
|
|
4
5
|
|
|
@@ -22,12 +23,27 @@ class Injectable:
|
|
|
22
23
|
# Dependency tracking
|
|
23
24
|
self.imports: set['Injectable'] = set()
|
|
24
25
|
self.dependent: set['Injectable'] = set()
|
|
26
|
+
self._weight: Optional[int] = None
|
|
25
27
|
|
|
26
28
|
# Validation flags
|
|
27
29
|
self.partial_resolution: bool = False
|
|
28
30
|
self.strict_resolution: bool = True
|
|
29
31
|
self.is_resolved: bool = False
|
|
30
32
|
|
|
33
|
+
def weight(self) -> int:
|
|
34
|
+
"""Calculate the weight of this injectable for graph visualization.
|
|
35
|
+
|
|
36
|
+
The weight is defined as the number of imports plus twice the number of
|
|
37
|
+
dependents. This heuristic emphasizes providers that are more central in
|
|
38
|
+
the dependency graph, as they have more dependents relying on them.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
int: The calculated weight of this injectable.
|
|
42
|
+
"""
|
|
43
|
+
if self._weight is None:
|
|
44
|
+
self._weight = len(self.imports) + sum(d.weight() for d in self.imports)
|
|
45
|
+
return self._weight
|
|
46
|
+
|
|
31
47
|
def has_implementation(self) -> bool:
|
|
32
48
|
"""Check if the implementation of this injectable is valid.
|
|
33
49
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dependency.core import Registry
|
|
2
|
+
from dependency.core.injection import ContainerInjection, ProviderInjection
|
|
3
|
+
from dependency.library.graph.models import Graph, Cluster, Node, Edge
|
|
4
|
+
|
|
5
|
+
def generate_graph(
|
|
6
|
+
output: str = "build/output",
|
|
7
|
+
ignore_modules: set[str] = {"BasePlugin"},
|
|
8
|
+
) -> None:
|
|
9
|
+
"""Generate a graph visualization of the registered containers and providers.
|
|
10
|
+
|
|
11
|
+
This method allows you to visualize the structure of your dependency graph, including the
|
|
12
|
+
containers (modules) and providers (components/products) and their relationships. The generated
|
|
13
|
+
graph can be used for debugging, documentation, or simply to understand the structure of your
|
|
14
|
+
dependency graph. The output will be saved as an SVG file at the specified location.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
output: The output path for the generated graph.
|
|
18
|
+
ignore_modules: A set of module names to ignore during graph generation.
|
|
19
|
+
"""
|
|
20
|
+
graph: Graph = Graph(name="Dependency Graph")
|
|
21
|
+
for container in Registry.containers:
|
|
22
|
+
if container.is_root:
|
|
23
|
+
graph.drawable.append(process_container(graph, container, ignore_modules))
|
|
24
|
+
|
|
25
|
+
digraph = graph.draw()
|
|
26
|
+
digraph.render(filename=output, format="svg") # type: ignore
|
|
27
|
+
|
|
28
|
+
def process_container(
|
|
29
|
+
graph: Graph,
|
|
30
|
+
container: ContainerInjection,
|
|
31
|
+
ignore_modules: set[str] = {"BasePlugin"},
|
|
32
|
+
) -> Cluster:
|
|
33
|
+
cluster = Cluster(name=container.name)
|
|
34
|
+
for child in container.childs:
|
|
35
|
+
if isinstance(child, ContainerInjection):
|
|
36
|
+
cluster.childs.append(process_container(graph, child, ignore_modules))
|
|
37
|
+
elif isinstance(child, ProviderInjection):
|
|
38
|
+
cluster.childs.append(process_provider(graph, child, ignore_modules))
|
|
39
|
+
return cluster
|
|
40
|
+
|
|
41
|
+
def process_provider(
|
|
42
|
+
graph: Graph,
|
|
43
|
+
provider: ProviderInjection,
|
|
44
|
+
ignore_modules: set[str] = {"BasePlugin"},
|
|
45
|
+
) -> Node:
|
|
46
|
+
if provider.parent is not None and str(provider.parent) in ignore_modules:
|
|
47
|
+
return Node(name=provider.name)
|
|
48
|
+
|
|
49
|
+
for dependent in provider.injectable.dependent:
|
|
50
|
+
source: str = provider.injectable.interface_cls.__name__
|
|
51
|
+
target: str = dependent.interface_cls.__name__
|
|
52
|
+
edge: Edge = Edge(source=source, target=target)
|
|
53
|
+
graph.edges.append(edge)
|
|
54
|
+
|
|
55
|
+
in_degree: int = provider.injectable.weight()
|
|
56
|
+
return Node(name=provider.name, in_degree=in_degree)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from itertools import groupby, pairwise
|
|
3
|
+
from graphviz import Digraph
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
GROUP_SIZE: int = 2
|
|
7
|
+
|
|
8
|
+
class Graph(BaseModel):
|
|
9
|
+
name: str = "Dependency Graph"
|
|
10
|
+
drawable: list[Drawable] = []
|
|
11
|
+
edges: list[Edge] = []
|
|
12
|
+
|
|
13
|
+
def draw(self) -> Digraph:
|
|
14
|
+
graph: Digraph = Digraph(comment=self.name, engine="dot")
|
|
15
|
+
graph.attr(rankdir="TB", newrank="true", ordering="in", overlap="false", splines="true", nodesep="1.0", ranksep="1.0")
|
|
16
|
+
graph.attr("node", fontname="Helvetica", fontsize="12", margin="0.2", style="invis")
|
|
17
|
+
|
|
18
|
+
for drawable in self.drawable:
|
|
19
|
+
drawable.draw(graph)
|
|
20
|
+
for edge in self.edges:
|
|
21
|
+
edge.draw(graph)
|
|
22
|
+
return graph
|
|
23
|
+
|
|
24
|
+
class Drawable(BaseModel, ABC):
|
|
25
|
+
name: str
|
|
26
|
+
in_degree: int = 0
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def draw(self, parent: Digraph) -> None:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
class Cluster(Drawable):
|
|
33
|
+
childs: list[Drawable] = []
|
|
34
|
+
style: dict[str, str] = {
|
|
35
|
+
"style": "rounded,filled",
|
|
36
|
+
"fillcolor": "lightyellow",
|
|
37
|
+
"color": "gray",
|
|
38
|
+
"penwidth": "2",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def draw(self, parent: Digraph) -> None:
|
|
42
|
+
with parent.subgraph(name=f"cluster_{self.name}") as c:
|
|
43
|
+
c.attr(label=self.name, **self.style)
|
|
44
|
+
|
|
45
|
+
# Agrupar por profundidad y ordenar por in_degree dentro de cada grupo
|
|
46
|
+
def bucket(x: Drawable): return x.in_degree // GROUP_SIZE
|
|
47
|
+
childs: list[Drawable] = sorted(self.childs, key=lambda c: c.in_degree)
|
|
48
|
+
groups = [list(g) for _, g in groupby(childs, key=bucket)]
|
|
49
|
+
|
|
50
|
+
for group in groups:
|
|
51
|
+
for child in group:
|
|
52
|
+
child.draw(c)
|
|
53
|
+
|
|
54
|
+
# Arista invisible entre nodos del mismo grupo para mantenerlos juntos
|
|
55
|
+
for i, (n1, n2) in enumerate(pairwise(group)):
|
|
56
|
+
if isinstance(n2, Node) and i % min(2, max(1, len(group) // GROUP_SIZE)) != 0:
|
|
57
|
+
c.edge(n1.name, n2.name, style="invis", weight="1")
|
|
58
|
+
|
|
59
|
+
# Arista invisible solo entre representantes de grupos consecutivos
|
|
60
|
+
for (g1, g2) in pairwise(groups):
|
|
61
|
+
c.edge(g1[0].name, g2[0].name, style="invis", weight="1")
|
|
62
|
+
|
|
63
|
+
class Node(Drawable):
|
|
64
|
+
style: dict[str, str] = {
|
|
65
|
+
"shape": "box",
|
|
66
|
+
"style": "filled",
|
|
67
|
+
"fillcolor": "white",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def draw(self, parent: Digraph) -> None:
|
|
71
|
+
parent.node(self.name, **self.style)
|
|
72
|
+
|
|
73
|
+
class Edge(BaseModel):
|
|
74
|
+
source: str
|
|
75
|
+
target: str
|
|
76
|
+
|
|
77
|
+
def draw(self, parent: Digraph) -> None:
|
|
78
|
+
kwargs: dict[str, str] = {}
|
|
79
|
+
parent.edge(self.source, self.target, weight="5", minlen="1", **kwargs)
|