module-dependency 1.1.2__tar.gz → 1.1.4__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/.github/workflows/docs.yaml +37 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/CHANGELOG.md +14 -3
- {module_dependency-1.1.2 → module_dependency-1.1.4}/PKG-INFO +1 -1
- module_dependency-1.1.4/docs/ARCHITECTURE.md +352 -0
- module_dependency-1.1.4/docs/reference/cli.md +6 -0
- module_dependency-1.1.4/docs/reference/core.md +32 -0
- module_dependency-1.1.4/mkdocs.yaml +44 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/pyproject.toml +17 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/agrupation/entrypoint.py +18 -0
- module_dependency-1.1.4/src/dependency/core/agrupation/fallback.py +48 -0
- module_dependency-1.1.4/src/dependency/core/agrupation/module.py +52 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/agrupation/plugin.py +15 -0
- module_dependency-1.1.4/src/dependency/core/declaration/component.py +95 -0
- module_dependency-1.1.4/src/dependency/core/declaration/instance.py +71 -0
- module_dependency-1.1.4/src/dependency/core/declaration/product.py +63 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/injection/injectable.py +55 -2
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/injection/injection.py +35 -4
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/injection/mixin.py +37 -13
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/injection/wiring.py +14 -0
- module_dependency-1.1.4/src/dependency/core/resolution/registry.py +63 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/resolution/resolver.py +7 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/resolution/strategy.py +8 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/utils/cycle.py +6 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/factory/__init__.py +6 -2
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/entrypoint.pyi +15 -1
- module_dependency-1.1.4/stubs/dependency/core/agrupation/fallback.pyi +27 -0
- module_dependency-1.1.4/stubs/dependency/core/agrupation/module.pyi +35 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/plugin.pyi +17 -2
- module_dependency-1.1.4/stubs/dependency/core/declaration/component.pyi +59 -0
- module_dependency-1.1.4/stubs/dependency/core/declaration/instance.pyi +40 -0
- module_dependency-1.1.4/stubs/dependency/core/declaration/product.pyi +50 -0
- module_dependency-1.1.4/stubs/dependency/core/injection/injectable.pyi +81 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/injection/injection.pyi +34 -3
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/injection/mixin.pyi +36 -12
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/injection/wiring.pyi +15 -1
- module_dependency-1.1.4/stubs/dependency/core/resolution/registry.pyi +50 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/resolution/resolver.pyi +7 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/resolution/strategy.pyi +8 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/utils/cycle.pyi +7 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_declaration.py +1 -1
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_interfaces.py +3 -3
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_providers.py +1 -1
- module_dependency-1.1.2/src/dependency/core/agrupation/module.py +0 -33
- module_dependency-1.1.2/src/dependency/core/declaration/component.py +0 -59
- module_dependency-1.1.2/src/dependency/core/declaration/instance.py +0 -46
- module_dependency-1.1.2/src/dependency/core/declaration/product.py +0 -40
- module_dependency-1.1.2/src/dependency/core/resolution/registry.py +0 -19
- module_dependency-1.1.2/stubs/dependency/core/agrupation/module.pyi +0 -21
- module_dependency-1.1.2/stubs/dependency/core/declaration/component.pyi +0 -29
- module_dependency-1.1.2/stubs/dependency/core/declaration/instance.pyi +0 -22
- module_dependency-1.1.2/stubs/dependency/core/declaration/product.pyi +0 -27
- module_dependency-1.1.2/stubs/dependency/core/injection/injectable.pyi +0 -28
- module_dependency-1.1.2/stubs/dependency/core/resolution/registry.pyi +0 -9
- {module_dependency-1.1.2 → module_dependency-1.1.4}/.github/workflows/release.yml +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/.github/workflows/testing.yml +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/.gitignore +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/.mypy.ini +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/.vscode/settings.json +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/LICENSE +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/README.md +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/docs/README.md +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/docs/REFERENCES.md +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/requirements.txt +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/base.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/component.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/instance.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/module.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/generation/plugin.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/models/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/models/base.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/templates/component.py.j2 +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/templates/instance.py.j2 +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/templates/module.py.j2 +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/cli/templates/plugin.py.j2 +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/agrupation/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/declaration/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/declaration/validation.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/exceptions.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/injection/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/resolution/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/resolution/container.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/resolution/errors.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/app/main/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/app/main/imports.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/app/main/plugins.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/config.json +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/concrete1.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/concrete2.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/builder/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/builder/concreteA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/builder/concreteB.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/factory/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/factory/concreteA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/creation/factory/concreteB.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/structural/bridge/abstraction/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/structural/bridge/abstraction/concrete.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/structural/bridge/implementation/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/module/structural/bridge/implementation/concrete.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/imports.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/number/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/number/fake.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/settings.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/string/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/base/string/fake.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/bridge/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/bridge/bridgeA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/events.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productB.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productC.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/providers/creatorA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/providers/creatorB.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/imports.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/interfaces.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/observer/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/observer/publisherA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/hardware/settings.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/facade/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/facade/facadeA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/factory/productA.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/imports.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/interfaces.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/example/plugin/reporter/settings.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/mixin/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/mixin/observer.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/mixin/state.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/patterns/__init__.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/patterns/composite.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/library/patterns/decorator.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/src/main.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/base.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/component.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/instance.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/module.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/generation/plugin.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/models/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/cli/models/base.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/declaration/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/declaration/validation.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/exceptions.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/injection/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/resolution/__init__.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/resolution/container.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/stubs/dependency/core/resolution/errors.pyi +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/cli/test_generation.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_agrupation.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_exceptions.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_injection.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_products.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_resolution.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_resource.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/core/test_validation.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/example/test_application.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/example/test_component.py +0 -0
- {module_dependency-1.1.2 → module_dependency-1.1.4}/tests/example/test_module.py +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Deploy Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
pages: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: pages
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
deploy-docs:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
environment:
|
|
20
|
+
name: github-pages
|
|
21
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: '3.13'
|
|
27
|
+
- name: Install Hatch
|
|
28
|
+
run: pipx install hatch
|
|
29
|
+
- name: Build documentation
|
|
30
|
+
run: hatch run docs:build
|
|
31
|
+
- name: Upload pages artifact
|
|
32
|
+
uses: actions/upload-pages-artifact@v3
|
|
33
|
+
with:
|
|
34
|
+
path: site/
|
|
35
|
+
- id: deployment
|
|
36
|
+
name: Deploy to GitHub Pages
|
|
37
|
+
uses: actions/deploy-pages@v4
|
|
@@ -5,17 +5,28 @@ 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.
|
|
8
|
+
## [v1.1.4] - 2026-03-19
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- MkDocs configuration for documentation site deployment to GitHub Pages
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Updated documentation and docstrings to reflect recent changes and improvements
|
|
17
|
+
|
|
18
|
+
## [v1.1.3] - 2026-03-16
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Support for provider classes in LazyWiring, allowing for more flexible and intuitive dependency injection configurations
|
|
23
|
+
- Fallback plugin will be used as parent for all orphans injectables, allowing for better handling of unregistered dependencies
|
|
13
24
|
|
|
14
25
|
### Changed
|
|
15
26
|
|
|
16
27
|
- LazyWiring now accepts provider classes directly, allowing for more intuitive usage
|
|
17
28
|
|
|
18
|
-
## [v1.1.2] - 2026-
|
|
29
|
+
## [v1.1.2] - 2026-03-16
|
|
19
30
|
|
|
20
31
|
### Added
|
|
21
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: module_dependency
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
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
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Architecture — module-dependency
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`module-dependency` is a dependency injection framework for modular Python applications,
|
|
6
|
+
built on top of [`dependency-injector`](https://python-dependency-injector.ets-labs.org/).
|
|
7
|
+
It adds a structured layer of abstraction that enforces modular design through a hierarchy
|
|
8
|
+
of organizational and providable units, with a resolution process that validates and wires
|
|
9
|
+
the full dependency graph before the application starts.
|
|
10
|
+
|
|
11
|
+
The framework is designed with embedded and long-running applications in mind: all
|
|
12
|
+
dependencies are declared statically, resolved eagerly at startup, and injected
|
|
13
|
+
transparently at runtime.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Mental Model
|
|
18
|
+
|
|
19
|
+
The framework organizes an application around two orthogonal concepts:
|
|
20
|
+
|
|
21
|
+
- **Structure**: how code is organized and grouped (Plugin, Module)
|
|
22
|
+
- **Providers**: how dependencies are declared and injected (Component, Instance, Product)
|
|
23
|
+
|
|
24
|
+
Every class in the framework is either a structural container or a providable unit,
|
|
25
|
+
and the two hierarchies mirror each other at runtime through the injection tree.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Core Concepts
|
|
30
|
+
|
|
31
|
+
### Plugin
|
|
32
|
+
|
|
33
|
+
A `Plugin` is the top-level structural unit. It represents a self-contained, reusable
|
|
34
|
+
feature of the application. Plugins are the entry points into the dependency graph — only
|
|
35
|
+
classes registered under a plugin (directly or through child modules) will be resolved
|
|
36
|
+
at startup.
|
|
37
|
+
|
|
38
|
+
Plugins can declare a typed `config` attribute (a Pydantic `BaseModel`), which is
|
|
39
|
+
automatically populated from the application `Container` during resolution.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
@module()
|
|
43
|
+
class HardwarePlugin(Plugin):
|
|
44
|
+
meta = PluginMeta(name="HardwarePlugin", version="0.1.0")
|
|
45
|
+
config: HardwarePluginConfig # populated from Container on resolution
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Plugins have no parent module — they are roots of the injection tree.
|
|
49
|
+
|
|
50
|
+
### Module
|
|
51
|
+
|
|
52
|
+
A `Module` groups related components under a plugin. Modules can be nested to represent
|
|
53
|
+
sub-features or layers within a plugin. They carry no logic themselves; their only role
|
|
54
|
+
is structural — to define scope and namespace within the injection tree.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
@module(module=HardwarePlugin)
|
|
58
|
+
class HardwareFactoryModule(Module):
|
|
59
|
+
pass
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Component
|
|
63
|
+
|
|
64
|
+
A `Component` declares an interface (or contract) for a dependency. It is the unit that
|
|
65
|
+
consumers depend on — other classes declare `Component` types in their `imports`, and
|
|
66
|
+
the framework ensures a concrete implementation is available before they run.
|
|
67
|
+
|
|
68
|
+
A component without a declared `provider` is purely an interface declaration: it can
|
|
69
|
+
be depended upon, but cannot be provided directly until an `Instance` implements it.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
@component(module=HardwarePlugin)
|
|
73
|
+
class HardwareFactory(ABC, Component):
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def createHardware(self, product: str) -> Hardware: ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Instance
|
|
79
|
+
|
|
80
|
+
An `Instance` is the concrete implementation of a `Component`. It inherits from the
|
|
81
|
+
component class and registers itself as the provider for that interface. Multiple
|
|
82
|
+
instances can be declared for the same component — the last one registered wins
|
|
83
|
+
(with a warning log).
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
@instance(
|
|
87
|
+
imports=[HardwareObserver, HardwareA, HardwareB],
|
|
88
|
+
provider=providers.Singleton,
|
|
89
|
+
)
|
|
90
|
+
class HardwareFactoryCreatorA(HardwareFactory):
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self.__factory = HardwareFactory.provide()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Product
|
|
96
|
+
|
|
97
|
+
A `Product` is functionally identical to a `Component` with `provider=providers.Factory`.
|
|
98
|
+
The distinction is semantic: Products represent objects that are instantiated on demand
|
|
99
|
+
(not managed as singletons), typically by a factory or service that creates them as
|
|
100
|
+
part of its operation.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
@product(
|
|
104
|
+
imports=[NumberService],
|
|
105
|
+
provider=providers.Factory,
|
|
106
|
+
)
|
|
107
|
+
class HardwareA(Hardware, Product):
|
|
108
|
+
@inject
|
|
109
|
+
def doStuff(self, operation: str, number: NumberService = LazyProvide[NumberService.reference]):
|
|
110
|
+
...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Products are declared as dependencies of the instances or other products that create
|
|
114
|
+
them — not consumed directly by end users of the framework.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Injection Hierarchy
|
|
119
|
+
|
|
120
|
+
The structural and injection trees are parallel. At the structural level:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Entrypoint
|
|
124
|
+
└── Plugin (root container)
|
|
125
|
+
└── Module (child container)
|
|
126
|
+
├── Component / Instance (provider)
|
|
127
|
+
└── Product (provider)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
At the injection level, this maps to a tree of `ContainerInjection` and
|
|
131
|
+
`ProviderInjection` objects:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
ContainerInjection (Plugin)
|
|
135
|
+
└── ContainerInjection (Module)
|
|
136
|
+
├── ProviderInjection (Component)
|
|
137
|
+
└── ProviderInjection (Product)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Each node in this tree knows its parent, and the full dot-separated path from root to
|
|
141
|
+
node is used as the `reference` string for `dependency-injector`'s wiring system
|
|
142
|
+
(e.g. `HardwarePlugin.HardwareFactory`).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Resolution Process
|
|
147
|
+
|
|
148
|
+
Resolution happens in five sequential phases, triggered by `Entrypoint.initialize()`:
|
|
149
|
+
|
|
150
|
+
### Phase 1 — Module Resolution (`resolve_modules`)
|
|
151
|
+
|
|
152
|
+
Each plugin's `ContainerInjection` tree is attached to the application `Container`.
|
|
153
|
+
Plugin configuration is validated and populated from the container config at this point.
|
|
154
|
+
This phase sets up the structural scaffolding before any providers are touched.
|
|
155
|
+
|
|
156
|
+
### Phase 2 — Injectable Collection (`resolve_injectables`)
|
|
157
|
+
|
|
158
|
+
Each plugin walks its injection tree and yields the `Injectable` objects for all
|
|
159
|
+
providers that have a valid implementation. Providers without implementation are silently
|
|
160
|
+
skipped (or warned, depending on `strict_resolution`).
|
|
161
|
+
|
|
162
|
+
### Phase 3 — Graph Expansion (`expand`)
|
|
163
|
+
|
|
164
|
+
Starting from the collected injectables, the resolver follows each provider's `imports`
|
|
165
|
+
recursively to discover the full transitive dependency graph. Providers marked with
|
|
166
|
+
`partial_resolution=True` are included in the set but their imports are not followed —
|
|
167
|
+
this is the escape hatch for providers that depend on external or optional components.
|
|
168
|
+
|
|
169
|
+
### Phase 4 — Topological Resolution (`injection`)
|
|
170
|
+
|
|
171
|
+
Dependencies are resolved in layers. In each iteration, all providers whose imports are
|
|
172
|
+
already resolved are marked as resolved. If an iteration produces no new resolved
|
|
173
|
+
providers, resolution has deadlocked — the error handler checks for circular dependencies
|
|
174
|
+
first, then reports missing implementations.
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Layer 1: providers with no imports → resolved
|
|
178
|
+
Layer 2: providers whose imports are all in Layer 1 → resolved
|
|
179
|
+
...
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Phase 5 — Wiring and Initialization
|
|
183
|
+
|
|
184
|
+
After all providers are resolved, their modules are wired into the `Container` using
|
|
185
|
+
`dependency-injector`'s wiring mechanism. Then, providers with `bootstrap=True` have
|
|
186
|
+
their implementation instantiated eagerly, triggering any `__init__` logic. If `__init__`
|
|
187
|
+
raises `CancelInitialization`, the bootstrap is skipped with a warning rather than
|
|
188
|
+
crashing the application.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## The Registry
|
|
193
|
+
|
|
194
|
+
The `Registry` is a global class-level store of all `ContainerInjection` and
|
|
195
|
+
`ProviderInjection` objects ever created. It is populated at decoration time (when
|
|
196
|
+
`@module`, `@component`, etc. are applied), before any resolution happens.
|
|
197
|
+
|
|
198
|
+
Its current role is diagnostic: `Registry.validation()` runs before resolution and
|
|
199
|
+
warns about any containers or providers that have no parent module (i.e., were declared
|
|
200
|
+
without being registered under any plugin or module). These are called "orphan"
|
|
201
|
+
providers, and the `FallbackPlugin` handles them at initialization time.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Dependency Injection at Runtime
|
|
206
|
+
|
|
207
|
+
Once resolved, dependencies can be accessed in two ways:
|
|
208
|
+
|
|
209
|
+
**Direct provision** — call `.provide()` on the component class. This returns the
|
|
210
|
+
underlying `dependency-injector` provider result (a singleton instance, a new factory
|
|
211
|
+
instance, etc.):
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
factory: HardwareFactory = HardwareFactory.provide()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**`@inject` with `LazyProvide`** — for method-level injection, use the `@inject`
|
|
218
|
+
decorator from `dependency-injector` combined with `LazyProvide`. The `Lazy` prefix
|
|
219
|
+
is required to defer the reference resolution to runtime rather than import time:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
@inject
|
|
223
|
+
def doStuff(self,
|
|
224
|
+
operation: str,
|
|
225
|
+
number: NumberService = LazyProvide[NumberService.reference],
|
|
226
|
+
) -> None:
|
|
227
|
+
...
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`LazyProvide`, `LazyProvider`, and `LazyClosing` mirror the standard `Provide`,
|
|
231
|
+
`Provider`, and `Closing` markers from `dependency-injector`, but accept either a
|
|
232
|
+
callable returning a reference string, or a `ProviderMixin` class directly (which
|
|
233
|
+
internally calls `.reference` on it).
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Provider Types
|
|
238
|
+
|
|
239
|
+
The framework supports the three provider types from `dependency-injector`:
|
|
240
|
+
|
|
241
|
+
| Type | Behavior | Typical use |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| `providers.Singleton` | One instance for the lifetime of the container | Services, observers, factories |
|
|
244
|
+
| `providers.Factory` | New instance on every `.provide()` call | Products, short-lived objects |
|
|
245
|
+
| `providers.Resource` | Singleton with context manager lifecycle (`__enter__`/`__exit__`) | Resources that need explicit cleanup |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Entrypoint
|
|
250
|
+
|
|
251
|
+
The `Entrypoint` class orchestrates the full startup sequence. The expected pattern is:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
class MyApplication(Entrypoint):
|
|
255
|
+
def __init__(self) -> None:
|
|
256
|
+
container = Container.from_dict(config={...})
|
|
257
|
+
super().__init__(container, PLUGINS)
|
|
258
|
+
|
|
259
|
+
# Import all instance modules here — this triggers @instance decoration,
|
|
260
|
+
# which registers implementations into the injection tree.
|
|
261
|
+
import my_app.imports
|
|
262
|
+
|
|
263
|
+
# Run the five-phase resolution process.
|
|
264
|
+
super().initialize()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The separation between `__init__` and `initialize()` is intentional: instance imports
|
|
268
|
+
must happen after the structural tree is set up (after `super().__init__`), but before
|
|
269
|
+
resolution starts (before `super().initialize()`).
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Imports File Convention
|
|
274
|
+
|
|
275
|
+
Because `@instance` and `@product` decorators register themselves at import time,
|
|
276
|
+
implementations must be imported before `initialize()` is called. The convention is
|
|
277
|
+
to collect all these imports in a dedicated `imports.py` file per plugin:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# example/plugin/hardware/imports.py
|
|
281
|
+
import example.plugin.hardware.bridge.bridgeA
|
|
282
|
+
import example.plugin.hardware.factory.providers.creatorA
|
|
283
|
+
import example.plugin.hardware.observer.publisherA
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
And then import all plugin imports files from the application's root `imports.py`:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
# app/main/imports.py
|
|
290
|
+
import example.plugin.base.imports
|
|
291
|
+
import example.plugin.hardware.imports
|
|
292
|
+
import example.plugin.reporter.imports
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
This pattern makes the set of active implementations explicit and easy to swap — for
|
|
296
|
+
example, to run in a test environment with fake implementations, you would create an
|
|
297
|
+
alternative imports file that imports the fakes instead.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Fallback Plugin
|
|
302
|
+
|
|
303
|
+
Providers that are declared without a parent module (orphan providers) would fail
|
|
304
|
+
resolution because they have no `ContainerInjection` to attach to, and therefore no
|
|
305
|
+
`reference` string for wiring. The `FallbackPlugin` is an internal plugin created
|
|
306
|
+
at initialization time that adopts all such orphan providers, attaches them to its
|
|
307
|
+
container, and marks them with `strict_resolution=False` so they do not block
|
|
308
|
+
resolution if they lack an implementation.
|
|
309
|
+
|
|
310
|
+
This is a safety mechanism, not a recommended pattern. Orphan providers are warned
|
|
311
|
+
about in `Registry.validation()` and should be assigned to a proper module.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Error Handling
|
|
316
|
+
|
|
317
|
+
| Exception | When raised |
|
|
318
|
+
|---|---|
|
|
319
|
+
| `DeclarationError` | A provider is accessed (`.provide()`, `.reference`) before being resolved, or has no implementation |
|
|
320
|
+
| `ResolutionError` | The topological resolution deadlocks (circular dependency or unresolved import) |
|
|
321
|
+
| `ProvisionError` | Plugin config validation fails, or a provider without a parent tries to build a reference |
|
|
322
|
+
| `InitializationError` | A bootstrapped provider's `__init__` raises an unexpected exception |
|
|
323
|
+
| `CancelInitialization` | Raised intentionally inside `__init__` to skip bootstrap without crashing |
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Internal Class Map
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
dependency.core
|
|
331
|
+
├── agrupation
|
|
332
|
+
│ ├── Entrypoint — application startup orchestrator
|
|
333
|
+
│ ├── Plugin — root structural unit, carries config
|
|
334
|
+
│ ├── Module — intermediate structural grouping
|
|
335
|
+
│ └── FallbackPlugin — internal, adopts orphan providers
|
|
336
|
+
├── declaration
|
|
337
|
+
│ ├── Component — interface declaration + ProviderMixin base
|
|
338
|
+
│ ├── Instance — concrete implementation of a Component
|
|
339
|
+
│ └── Product — Component alias, Factory default, legacy support
|
|
340
|
+
├── injection
|
|
341
|
+
│ ├── Injectable — tracks implementation, imports, resolution state
|
|
342
|
+
│ ├── ContainerInjection — injection node for structural units (Module/Plugin)
|
|
343
|
+
│ ├── ProviderInjection — injection node for providable units (Component/Product)
|
|
344
|
+
│ ├── ContainerMixin — class-level methods for structural units
|
|
345
|
+
│ ├── ProviderMixin — class-level methods for providable units
|
|
346
|
+
│ └── LazyProvide/Provider/Closing — deferred wiring markers
|
|
347
|
+
└── resolution
|
|
348
|
+
├── Container — DynamicContainer with config helpers
|
|
349
|
+
├── Registry — global store of all injection nodes
|
|
350
|
+
├── InjectionResolver — orchestrates the five resolution phases
|
|
351
|
+
└── ResolutionStrategy — implements expand, injection, wiring, initialize
|
|
352
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Core Reference
|
|
2
|
+
|
|
3
|
+
## Agrupation
|
|
4
|
+
|
|
5
|
+
::: dependency.core.agrupation.entrypoint
|
|
6
|
+
::: dependency.core.agrupation.plugin
|
|
7
|
+
::: dependency.core.agrupation.module
|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
::: dependency.core.declaration.component
|
|
12
|
+
::: dependency.core.declaration.instance
|
|
13
|
+
::: dependency.core.declaration.product
|
|
14
|
+
|
|
15
|
+
## Injection
|
|
16
|
+
|
|
17
|
+
::: dependency.core.injection.mixin
|
|
18
|
+
::: dependency.core.injection.injectable
|
|
19
|
+
::: dependency.core.injection.injection
|
|
20
|
+
::: dependency.core.injection.wiring
|
|
21
|
+
|
|
22
|
+
## Resolution
|
|
23
|
+
|
|
24
|
+
::: dependency.core.resolution.resolver
|
|
25
|
+
::: dependency.core.resolution.strategy
|
|
26
|
+
::: dependency.core.resolution.registry
|
|
27
|
+
::: dependency.core.resolution.container
|
|
28
|
+
::: dependency.core.resolution.errors
|
|
29
|
+
|
|
30
|
+
## Exceptions
|
|
31
|
+
|
|
32
|
+
::: dependency.core.exceptions
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
site_name: module-dependency
|
|
2
|
+
site_description: A Dependency Injection Framework for Modular Embedded Python Applications
|
|
3
|
+
site_url: https://fabaindaiz.github.io/module-dependency/
|
|
4
|
+
repo_name: module-dependency
|
|
5
|
+
repo_url: https://github.com/tu-usuario/module-dependency
|
|
6
|
+
docs_dir: docs
|
|
7
|
+
strict: true
|
|
8
|
+
|
|
9
|
+
theme:
|
|
10
|
+
name: material
|
|
11
|
+
features:
|
|
12
|
+
- navigation.sections
|
|
13
|
+
- navigation.indexes
|
|
14
|
+
- navigation.top
|
|
15
|
+
- toc.integrate
|
|
16
|
+
- content.code.copy
|
|
17
|
+
- content.code.annotate
|
|
18
|
+
|
|
19
|
+
plugins:
|
|
20
|
+
- search
|
|
21
|
+
- mkdocstrings:
|
|
22
|
+
handlers:
|
|
23
|
+
python:
|
|
24
|
+
paths: [src]
|
|
25
|
+
options:
|
|
26
|
+
docstring_style: google
|
|
27
|
+
show_source: false
|
|
28
|
+
show_root_heading: true
|
|
29
|
+
show_symbol_type_heading: true
|
|
30
|
+
show_symbol_type_toc: true
|
|
31
|
+
members_order: source
|
|
32
|
+
merge_init_into_class: true
|
|
33
|
+
filters:
|
|
34
|
+
- "!^_"
|
|
35
|
+
- "^__init__"
|
|
36
|
+
- "^__repr__"
|
|
37
|
+
|
|
38
|
+
nav:
|
|
39
|
+
- Overview: README.md
|
|
40
|
+
- Architecture: ARCHITECTURE.md
|
|
41
|
+
- API Reference:
|
|
42
|
+
- Core: reference/core.md
|
|
43
|
+
#- CLI: reference/cli.md
|
|
44
|
+
- Bibliography: REFERENCES.md
|
|
@@ -33,6 +33,22 @@ stubs = "stubgen src/dependency -o stubs --include-docstrings"
|
|
|
33
33
|
tests = "pytest -n auto --dist=loadfile"
|
|
34
34
|
typecheck = "mypy --strict src/dependency"
|
|
35
35
|
|
|
36
|
+
[tool.hatch.envs.docs]
|
|
37
|
+
template = "default"
|
|
38
|
+
skip-install = false
|
|
39
|
+
dependencies = [
|
|
40
|
+
"mkdocs",
|
|
41
|
+
"mkdocs-material",
|
|
42
|
+
"mkdocstrings[python]",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.envs.docs.env-vars]
|
|
46
|
+
PYTHONPATH = "src"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.envs.docs.scripts]
|
|
49
|
+
build = "mkdocs build"
|
|
50
|
+
serve = "mkdocs serve"
|
|
51
|
+
|
|
36
52
|
[tool.mypy]
|
|
37
53
|
plugins = [
|
|
38
54
|
"pydantic.mypy",
|
|
@@ -50,7 +66,7 @@ testpaths = ["tests"]
|
|
|
50
66
|
|
|
51
67
|
[project]
|
|
52
68
|
name = "module_dependency"
|
|
53
|
-
version = "1.1.
|
|
69
|
+
version = "1.1.4"
|
|
54
70
|
dependencies = [
|
|
55
71
|
"dependency_injector",
|
|
56
72
|
"jinja2",
|
{module_dependency-1.1.2 → module_dependency-1.1.4}/src/dependency/core/agrupation/entrypoint.py
RENAMED
|
@@ -5,6 +5,7 @@ from typing import Iterable
|
|
|
5
5
|
from dependency.core.agrupation.plugin import Plugin
|
|
6
6
|
from dependency.core.injection.injectable import Injectable
|
|
7
7
|
from dependency.core.resolution.container import Container
|
|
8
|
+
from dependency.core.agrupation.fallback import initialize_fallback
|
|
8
9
|
from dependency.core.resolution.resolver import InjectionResolver
|
|
9
10
|
from dependency.core.resolution.strategy import ResolutionStrategy
|
|
10
11
|
_logger = logging.getLogger("dependency.loader")
|
|
@@ -20,6 +21,19 @@ class Entrypoint:
|
|
|
20
21
|
plugins: Iterable[type[Plugin]],
|
|
21
22
|
strategy: ResolutionStrategy = ResolutionStrategy(),
|
|
22
23
|
) -> None:
|
|
24
|
+
"""Set up the application entrypoint.
|
|
25
|
+
|
|
26
|
+
Stores the plugin list and initializes the InjectionResolver with the
|
|
27
|
+
given container. Resolves the structural dependency tree so that the
|
|
28
|
+
injection hierarchy is ready before instance imports loaded.
|
|
29
|
+
Full resolution is deferred to initialize().
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
container (Container): The application container holding configuration.
|
|
33
|
+
plugins (Iterable[type[Plugin]]): List of root Plugin classes to load.
|
|
34
|
+
strategy (ResolutionStrategy): Resolution strategy to use. Defaults to
|
|
35
|
+
a standard ResolutionStrategy with default config.
|
|
36
|
+
"""
|
|
23
37
|
self.init_time: float = time.time()
|
|
24
38
|
self.modules: list[type[Plugin]] = list(plugins)
|
|
25
39
|
self.strategy: ResolutionStrategy = strategy
|
|
@@ -41,6 +55,10 @@ class Entrypoint:
|
|
|
41
55
|
injectables: Iterable[Injectable] = (),
|
|
42
56
|
) -> None:
|
|
43
57
|
"""Initialize the application."""
|
|
58
|
+
if self.strategy.config.init_fallback:
|
|
59
|
+
initialize_fallback(parent=self.resolver.container)
|
|
60
|
+
_logger.info("Fallback plugin initialized")
|
|
61
|
+
|
|
44
62
|
providers: set[Injectable] = self.resolver.resolve_injectables(
|
|
45
63
|
modules=self.modules,
|
|
46
64
|
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dependency_injector import containers
|
|
3
|
+
from dependency.core.agrupation.module import module
|
|
4
|
+
from dependency.core.agrupation.plugin import Plugin, PluginMeta
|
|
5
|
+
from dependency.core.resolution.registry import Registry
|
|
6
|
+
_logger = logging.getLogger("dependency.loader")
|
|
7
|
+
|
|
8
|
+
def initialize_fallback(parent: containers.Container) -> type[Plugin]:
|
|
9
|
+
"""Create and initialize the internal FallbackPlugin.
|
|
10
|
+
|
|
11
|
+
The FallbackPlugin adopts all orphan providers — providers that were declared
|
|
12
|
+
without a parent module and therefore have no ContainerInjection node in the
|
|
13
|
+
tree. Without adoption, these providers would fail resolution because they
|
|
14
|
+
cannot build a reference string for wiring.
|
|
15
|
+
|
|
16
|
+
Orphan providers are attached to the FallbackPlugin's container, marked with
|
|
17
|
+
strict_resolution=False so they do not block resolution if they lack an
|
|
18
|
+
implementation, and their providers are resolved against the parent container.
|
|
19
|
+
|
|
20
|
+
This function is called once during Entrypoint.initialize() before the main
|
|
21
|
+
resolution phases run. It is an internal safety mechanism — orphan providers
|
|
22
|
+
should ideally be assigned to a proper module.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
parent (containers.Container): The root application container.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
type[Plugin]: The dynamically created FallbackInternal plugin class.
|
|
29
|
+
"""
|
|
30
|
+
@module()
|
|
31
|
+
class FallbackInternal(Plugin):
|
|
32
|
+
meta = PluginMeta(name="FallbackPlugin", version="1.0.0")
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def initialize_fallback(cls, parent: containers.Container) -> None:
|
|
36
|
+
#for container in Registry.containers:
|
|
37
|
+
# if container.parent is None and not container.is_root:
|
|
38
|
+
# _logger.debug(f"Container {container} has been registered to fallback module")
|
|
39
|
+
# container.change_parent(cls.injection)
|
|
40
|
+
for provider in Registry.providers:
|
|
41
|
+
if provider.parent is None and not provider.is_root:
|
|
42
|
+
_logger.debug(f"Provider {provider} has been registered to fallback module")
|
|
43
|
+
provider.injectable.strict_resolution = False
|
|
44
|
+
provider.change_parent(cls.injection)
|
|
45
|
+
cls.resolve_providers(container=parent)
|
|
46
|
+
|
|
47
|
+
FallbackInternal.initialize_fallback(parent=parent)
|
|
48
|
+
return FallbackInternal
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Callable, Optional, TypeVar
|
|
2
|
+
from dependency.core.injection.mixin import ContainerMixin
|
|
3
|
+
|
|
4
|
+
MODULE = TypeVar('MODULE', bound='Module')
|
|
5
|
+
|
|
6
|
+
class Module(ContainerMixin):
|
|
7
|
+
"""Base class for all structural grouping units in the framework.
|
|
8
|
+
|
|
9
|
+
A Module organizes related Components and Products under a common namespace
|
|
10
|
+
within the injection tree. It carries no logic of its own — its only role is
|
|
11
|
+
structural: to define scope and hierarchy for the providers registered under it.
|
|
12
|
+
|
|
13
|
+
Modules must be decorated with @module to be registered in the injection tree.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def module(
|
|
17
|
+
module: Optional[type[Module]] = None
|
|
18
|
+
) -> Callable[[type[MODULE]], type[MODULE]]:
|
|
19
|
+
"""Register a Module class into the injection tree.
|
|
20
|
+
|
|
21
|
+
Initializes a ContainerInjection node for the decorated class and attaches
|
|
22
|
+
it to the parent module's injection node if one is provided. The decorated
|
|
23
|
+
class must be a subclass of Module.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
module (type[Module], optional): Parent module or plugin this module
|
|
27
|
+
belongs to. If None, the module is registered without a parent —
|
|
28
|
+
it will be treated as an orphan unless it is itself a Plugin root.
|
|
29
|
+
Defaults to None.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
TypeError: If the decorated class is not a subclass of Module.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Callable[[type[MODULE]], type[MODULE]]: Decorator that registers the
|
|
36
|
+
module class and returns it unchanged.
|
|
37
|
+
"""
|
|
38
|
+
def wrap(cls: type[MODULE]) -> type[MODULE]:
|
|
39
|
+
"""Register the module class into the injection tree.
|
|
40
|
+
|
|
41
|
+
Initializes the ContainerInjection for the class and attaches it to the
|
|
42
|
+
parent module's injection node if one was provided.
|
|
43
|
+
"""
|
|
44
|
+
if not issubclass(cls, Module):
|
|
45
|
+
raise TypeError(f"Class {cls} has decorator @module but is not a subclass of Module") # pragma: no cover
|
|
46
|
+
|
|
47
|
+
cls.init_injection(
|
|
48
|
+
parent=module.injection if module else None
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return cls
|
|
52
|
+
return wrap
|