module-dependency 1.1.3__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.
Files changed (171) hide show
  1. module_dependency-1.1.4/.github/workflows/docs.yaml +37 -0
  2. {module_dependency-1.1.3 → module_dependency-1.1.4}/CHANGELOG.md +14 -3
  3. {module_dependency-1.1.3 → module_dependency-1.1.4}/PKG-INFO +1 -1
  4. module_dependency-1.1.4/docs/ARCHITECTURE.md +352 -0
  5. module_dependency-1.1.4/docs/reference/cli.md +6 -0
  6. module_dependency-1.1.4/docs/reference/core.md +32 -0
  7. module_dependency-1.1.4/mkdocs.yaml +44 -0
  8. {module_dependency-1.1.3 → module_dependency-1.1.4}/pyproject.toml +17 -1
  9. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/agrupation/entrypoint.py +13 -0
  10. module_dependency-1.1.4/src/dependency/core/agrupation/fallback.py +48 -0
  11. module_dependency-1.1.4/src/dependency/core/agrupation/module.py +52 -0
  12. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/agrupation/plugin.py +15 -0
  13. module_dependency-1.1.4/src/dependency/core/declaration/component.py +95 -0
  14. module_dependency-1.1.4/src/dependency/core/declaration/instance.py +71 -0
  15. module_dependency-1.1.4/src/dependency/core/declaration/product.py +63 -0
  16. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/injection/injectable.py +55 -2
  17. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/injection/injection.py +35 -4
  18. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/injection/mixin.py +35 -12
  19. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/injection/wiring.py +14 -0
  20. module_dependency-1.1.4/src/dependency/core/resolution/registry.py +63 -0
  21. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/resolution/resolver.py +6 -0
  22. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/resolution/strategy.py +7 -1
  23. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/utils/cycle.py +6 -0
  24. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/entrypoint.pyi +14 -1
  25. module_dependency-1.1.4/stubs/dependency/core/agrupation/fallback.pyi +27 -0
  26. module_dependency-1.1.4/stubs/dependency/core/agrupation/module.pyi +35 -0
  27. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/plugin.pyi +17 -2
  28. module_dependency-1.1.4/stubs/dependency/core/declaration/component.pyi +59 -0
  29. module_dependency-1.1.4/stubs/dependency/core/declaration/instance.pyi +40 -0
  30. module_dependency-1.1.4/stubs/dependency/core/declaration/product.pyi +50 -0
  31. module_dependency-1.1.4/stubs/dependency/core/injection/injectable.pyi +81 -0
  32. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/injection/injection.pyi +34 -3
  33. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/injection/mixin.pyi +36 -12
  34. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/injection/wiring.pyi +15 -1
  35. module_dependency-1.1.4/stubs/dependency/core/resolution/registry.pyi +50 -0
  36. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/resolution/resolver.pyi +7 -1
  37. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/resolution/strategy.pyi +7 -1
  38. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/utils/cycle.pyi +7 -1
  39. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_declaration.py +1 -1
  40. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_interfaces.py +3 -3
  41. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_providers.py +1 -1
  42. module_dependency-1.1.3/src/dependency/core/agrupation/fallback.py +0 -27
  43. module_dependency-1.1.3/src/dependency/core/agrupation/module.py +0 -33
  44. module_dependency-1.1.3/src/dependency/core/declaration/component.py +0 -59
  45. module_dependency-1.1.3/src/dependency/core/declaration/instance.py +0 -46
  46. module_dependency-1.1.3/src/dependency/core/declaration/product.py +0 -40
  47. module_dependency-1.1.3/src/dependency/core/resolution/registry.py +0 -24
  48. module_dependency-1.1.3/stubs/dependency/core/agrupation/fallback.pyi +0 -6
  49. module_dependency-1.1.3/stubs/dependency/core/agrupation/module.pyi +0 -21
  50. module_dependency-1.1.3/stubs/dependency/core/declaration/component.pyi +0 -29
  51. module_dependency-1.1.3/stubs/dependency/core/declaration/instance.pyi +0 -22
  52. module_dependency-1.1.3/stubs/dependency/core/declaration/product.pyi +0 -27
  53. module_dependency-1.1.3/stubs/dependency/core/injection/injectable.pyi +0 -28
  54. module_dependency-1.1.3/stubs/dependency/core/resolution/registry.pyi +0 -11
  55. {module_dependency-1.1.3 → module_dependency-1.1.4}/.github/workflows/release.yml +0 -0
  56. {module_dependency-1.1.3 → module_dependency-1.1.4}/.github/workflows/testing.yml +0 -0
  57. {module_dependency-1.1.3 → module_dependency-1.1.4}/.gitignore +0 -0
  58. {module_dependency-1.1.3 → module_dependency-1.1.4}/.mypy.ini +0 -0
  59. {module_dependency-1.1.3 → module_dependency-1.1.4}/.vscode/settings.json +0 -0
  60. {module_dependency-1.1.3 → module_dependency-1.1.4}/LICENSE +0 -0
  61. {module_dependency-1.1.3 → module_dependency-1.1.4}/README.md +0 -0
  62. {module_dependency-1.1.3 → module_dependency-1.1.4}/docs/README.md +0 -0
  63. {module_dependency-1.1.3 → module_dependency-1.1.4}/docs/REFERENCES.md +0 -0
  64. {module_dependency-1.1.3 → module_dependency-1.1.4}/requirements.txt +0 -0
  65. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/__init__.py +0 -0
  66. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/__init__.py +0 -0
  67. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/__init__.py +0 -0
  68. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/base.py +0 -0
  69. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/component.py +0 -0
  70. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/instance.py +0 -0
  71. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/module.py +0 -0
  72. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/generation/plugin.py +0 -0
  73. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/models/__init__.py +0 -0
  74. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/models/base.py +0 -0
  75. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/templates/component.py.j2 +0 -0
  76. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/templates/instance.py.j2 +0 -0
  77. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/templates/module.py.j2 +0 -0
  78. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/cli/templates/plugin.py.j2 +0 -0
  79. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/__init__.py +0 -0
  80. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/agrupation/__init__.py +0 -0
  81. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/declaration/__init__.py +0 -0
  82. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/declaration/validation.py +0 -0
  83. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/exceptions.py +0 -0
  84. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/injection/__init__.py +0 -0
  85. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/resolution/__init__.py +0 -0
  86. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/resolution/container.py +0 -0
  87. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/dependency/core/resolution/errors.py +0 -0
  88. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/app/main/__init__.py +0 -0
  89. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/app/main/imports.py +0 -0
  90. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/app/main/plugins.py +0 -0
  91. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/config.json +0 -0
  92. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/__init__.py +0 -0
  93. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/concrete1.py +0 -0
  94. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/abstract_factory/concrete2.py +0 -0
  95. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/builder/__init__.py +0 -0
  96. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/builder/concreteA.py +0 -0
  97. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/builder/concreteB.py +0 -0
  98. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/factory/__init__.py +0 -0
  99. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/factory/concreteA.py +0 -0
  100. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/creation/factory/concreteB.py +0 -0
  101. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/structural/bridge/abstraction/__init__.py +0 -0
  102. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/structural/bridge/abstraction/concrete.py +0 -0
  103. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/structural/bridge/implementation/__init__.py +0 -0
  104. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/module/structural/bridge/implementation/concrete.py +0 -0
  105. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/__init__.py +0 -0
  106. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/imports.py +0 -0
  107. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/number/__init__.py +0 -0
  108. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/number/fake.py +0 -0
  109. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/settings.py +0 -0
  110. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/string/__init__.py +0 -0
  111. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/base/string/fake.py +0 -0
  112. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/__init__.py +0 -0
  113. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/bridge/__init__.py +0 -0
  114. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/bridge/bridgeA.py +0 -0
  115. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/events.py +0 -0
  116. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/__init__.py +0 -0
  117. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productA.py +0 -0
  118. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productB.py +0 -0
  119. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/products/productC.py +0 -0
  120. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/providers/creatorA.py +0 -0
  121. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/factory/providers/creatorB.py +0 -0
  122. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/imports.py +0 -0
  123. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/interfaces.py +0 -0
  124. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/observer/__init__.py +0 -0
  125. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/observer/publisherA.py +0 -0
  126. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/hardware/settings.py +0 -0
  127. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/__init__.py +0 -0
  128. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/facade/__init__.py +0 -0
  129. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/facade/facadeA.py +0 -0
  130. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/factory/__init__.py +0 -0
  131. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/factory/productA.py +0 -0
  132. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/imports.py +0 -0
  133. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/interfaces.py +0 -0
  134. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/example/plugin/reporter/settings.py +0 -0
  135. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/mixin/__init__.py +0 -0
  136. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/mixin/observer.py +0 -0
  137. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/mixin/state.py +0 -0
  138. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/patterns/__init__.py +0 -0
  139. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/patterns/composite.py +0 -0
  140. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/library/patterns/decorator.py +0 -0
  141. {module_dependency-1.1.3 → module_dependency-1.1.4}/src/main.py +0 -0
  142. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/__init__.pyi +0 -0
  143. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/__init__.pyi +0 -0
  144. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/__init__.pyi +0 -0
  145. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/base.pyi +0 -0
  146. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/component.pyi +0 -0
  147. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/instance.pyi +0 -0
  148. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/module.pyi +0 -0
  149. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/generation/plugin.pyi +0 -0
  150. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/models/__init__.pyi +0 -0
  151. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/cli/models/base.pyi +0 -0
  152. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/__init__.pyi +0 -0
  153. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/agrupation/__init__.pyi +0 -0
  154. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/declaration/__init__.pyi +0 -0
  155. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/declaration/validation.pyi +0 -0
  156. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/exceptions.pyi +0 -0
  157. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/injection/__init__.pyi +0 -0
  158. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/resolution/__init__.pyi +0 -0
  159. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/resolution/container.pyi +0 -0
  160. {module_dependency-1.1.3 → module_dependency-1.1.4}/stubs/dependency/core/resolution/errors.pyi +0 -0
  161. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/cli/test_generation.py +0 -0
  162. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_agrupation.py +0 -0
  163. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_exceptions.py +0 -0
  164. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_injection.py +0 -0
  165. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_products.py +0 -0
  166. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_resolution.py +0 -0
  167. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_resource.py +0 -0
  168. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/core/test_validation.py +0 -0
  169. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/example/test_application.py +0 -0
  170. {module_dependency-1.1.3 → module_dependency-1.1.4}/tests/example/test_component.py +0 -0
  171. {module_dependency-1.1.3 → 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.3] - 2026-02-
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-02-
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
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,6 @@
1
+ # CLI Reference
2
+
3
+ ::: dependency.cli.generation.plugin
4
+ ::: dependency.cli.generation.module
5
+ ::: dependency.cli.generation.component
6
+ ::: dependency.cli.generation.instance
@@ -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.3"
69
+ version = "1.1.4"
54
70
  dependencies = [
55
71
  "dependency_injector",
56
72
  "jinja2",
@@ -21,6 +21,19 @@ class Entrypoint:
21
21
  plugins: Iterable[type[Plugin]],
22
22
  strategy: ResolutionStrategy = ResolutionStrategy(),
23
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
+ """
24
37
  self.init_time: float = time.time()
25
38
  self.modules: list[type[Plugin]] = list(plugins)
26
39
  self.strategy: ResolutionStrategy = strategy
@@ -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