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.
Files changed (179) hide show
  1. {module_dependency-1.1.4 → module_dependency-1.1.6}/CHANGELOG.md +7 -0
  2. {module_dependency-1.1.4 → module_dependency-1.1.6}/PKG-INFO +12 -14
  3. module_dependency-1.1.6/README.md +251 -0
  4. {module_dependency-1.1.4 → module_dependency-1.1.6/docs}/README.md +3 -1
  5. {module_dependency-1.1.4 → module_dependency-1.1.6}/mkdocs.yaml +1 -1
  6. {module_dependency-1.1.4 → module_dependency-1.1.6}/pyproject.toml +8 -5
  7. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/entrypoint.py +6 -1
  8. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/injectable.py +16 -0
  9. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/utils/cycle.py +1 -1
  10. module_dependency-1.1.6/src/dependency/library/graph/__init__.py +5 -0
  11. module_dependency-1.1.6/src/dependency/library/graph/generate.py +56 -0
  12. module_dependency-1.1.6/src/dependency/library/graph/models.py +79 -0
  13. {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/observer.py +25 -22
  14. {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/state.py +2 -2
  15. module_dependency-1.1.6/src/dependency/library/threading.py +44 -0
  16. module_dependency-1.1.6/src/example/diagram.svg +143 -0
  17. module_dependency-1.1.6/src/example/plugin/base/deferred/__init__.py +81 -0
  18. module_dependency-1.1.6/src/example/plugin/base/deferred/interfaces.py +14 -0
  19. module_dependency-1.1.6/src/example/plugin/base/deferred/uvloop.py +47 -0
  20. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/imports.py +1 -0
  21. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/events.py +1 -1
  22. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productA.py +3 -1
  23. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productB.py +3 -1
  24. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/products/productC.py +2 -0
  25. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/observer/__init__.py +2 -1
  26. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/observer/publisherA.py +9 -4
  27. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/factory/productA.py +6 -3
  28. module_dependency-1.1.6/src/graph.py +6 -0
  29. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/entrypoint.pyi +6 -1
  30. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/injectable.pyi +10 -0
  31. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/mixin.pyi +0 -1
  32. module_dependency-1.1.6/stubs/dependency/library/graph/__init__.pyi +3 -0
  33. module_dependency-1.1.6/stubs/dependency/library/graph/generate.pyi +14 -0
  34. module_dependency-1.1.6/stubs/dependency/library/graph/models.pyi +32 -0
  35. module_dependency-1.1.6/stubs/dependency/library/graph/utils.pyi +3 -0
  36. module_dependency-1.1.6/stubs/dependency/library/patterns/composite.pyi +10 -0
  37. module_dependency-1.1.6/stubs/dependency/library/patterns/observer.pyi +15 -0
  38. module_dependency-1.1.6/stubs/dependency/library/patterns/state.pyi +10 -0
  39. module_dependency-1.1.6/stubs/dependency/library/threading.pyi +9 -0
  40. module_dependency-1.1.6/tests/core/test_agrupation.py +49 -0
  41. module_dependency-1.1.6/tests/core/test_declaration.py +87 -0
  42. module_dependency-1.1.6/tests/core/test_detectcycle.py +65 -0
  43. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_exceptions.py +11 -1
  44. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_injection.py +30 -0
  45. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_interfaces.py +1 -0
  46. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_products.py +1 -0
  47. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_providers.py +1 -0
  48. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_resolution.py +1 -0
  49. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_resource.py +1 -0
  50. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/core/test_validation.py +14 -0
  51. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/example/test_component.py +2 -0
  52. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/example/test_module.py +2 -0
  53. module_dependency-1.1.4/docs/README.md +0 -2
  54. module_dependency-1.1.4/src/library/patterns/decorator.py +0 -7
  55. module_dependency-1.1.4/tests/core/test_agrupation.py +0 -21
  56. module_dependency-1.1.4/tests/core/test_declaration.py +0 -39
  57. {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/docs.yaml +0 -0
  58. {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/release.yml +0 -0
  59. {module_dependency-1.1.4 → module_dependency-1.1.6}/.github/workflows/testing.yml +0 -0
  60. {module_dependency-1.1.4 → module_dependency-1.1.6}/.gitignore +0 -0
  61. {module_dependency-1.1.4 → module_dependency-1.1.6}/.mypy.ini +0 -0
  62. {module_dependency-1.1.4 → module_dependency-1.1.6}/.vscode/settings.json +0 -0
  63. {module_dependency-1.1.4 → module_dependency-1.1.6}/LICENSE +0 -0
  64. {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/ARCHITECTURE.md +0 -0
  65. {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/REFERENCES.md +0 -0
  66. {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/reference/cli.md +0 -0
  67. {module_dependency-1.1.4 → module_dependency-1.1.6}/docs/reference/core.md +0 -0
  68. {module_dependency-1.1.4 → module_dependency-1.1.6}/requirements.txt +0 -0
  69. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/__init__.py +0 -0
  70. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/__init__.py +0 -0
  71. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/__init__.py +0 -0
  72. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/base.py +0 -0
  73. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/component.py +0 -0
  74. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/instance.py +0 -0
  75. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/module.py +0 -0
  76. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/generation/plugin.py +0 -0
  77. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/models/__init__.py +0 -0
  78. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/models/base.py +0 -0
  79. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/component.py.j2 +0 -0
  80. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/instance.py.j2 +0 -0
  81. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/module.py.j2 +0 -0
  82. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/cli/templates/plugin.py.j2 +0 -0
  83. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/__init__.py +0 -0
  84. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/__init__.py +0 -0
  85. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/fallback.py +0 -0
  86. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/module.py +0 -0
  87. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/agrupation/plugin.py +0 -0
  88. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/__init__.py +0 -0
  89. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/component.py +0 -0
  90. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/instance.py +0 -0
  91. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/product.py +0 -0
  92. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/declaration/validation.py +0 -0
  93. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/exceptions.py +0 -0
  94. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/__init__.py +0 -0
  95. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/injection.py +0 -0
  96. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/mixin.py +0 -0
  97. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/injection/wiring.py +0 -0
  98. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/__init__.py +0 -0
  99. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/container.py +0 -0
  100. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/errors.py +0 -0
  101. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/registry.py +0 -0
  102. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/resolver.py +0 -0
  103. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/dependency/core/resolution/strategy.py +0 -0
  104. {module_dependency-1.1.4/src/library/mixin → module_dependency-1.1.6/src/dependency/library/patterns}/__init__.py +0 -0
  105. {module_dependency-1.1.4/src → module_dependency-1.1.6/src/dependency}/library/patterns/composite.py +0 -0
  106. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/__init__.py +0 -0
  107. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/imports.py +0 -0
  108. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/app/main/plugins.py +0 -0
  109. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/config.json +0 -0
  110. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/__init__.py +0 -0
  111. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/concrete1.py +0 -0
  112. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/abstract_factory/concrete2.py +0 -0
  113. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/__init__.py +0 -0
  114. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/concreteA.py +0 -0
  115. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/builder/concreteB.py +0 -0
  116. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/__init__.py +0 -0
  117. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/concreteA.py +0 -0
  118. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/creation/factory/concreteB.py +0 -0
  119. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/abstraction/__init__.py +0 -0
  120. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/abstraction/concrete.py +0 -0
  121. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/implementation/__init__.py +0 -0
  122. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/module/structural/bridge/implementation/concrete.py +0 -0
  123. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/__init__.py +0 -0
  124. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/number/__init__.py +0 -0
  125. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/number/fake.py +0 -0
  126. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/settings.py +0 -0
  127. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/string/__init__.py +0 -0
  128. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/base/string/fake.py +0 -0
  129. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/__init__.py +0 -0
  130. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/bridge/__init__.py +0 -0
  131. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/bridge/bridgeA.py +0 -0
  132. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/__init__.py +0 -0
  133. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/providers/creatorA.py +0 -0
  134. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/factory/providers/creatorB.py +0 -0
  135. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/imports.py +0 -0
  136. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/interfaces.py +0 -0
  137. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/hardware/settings.py +0 -0
  138. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/__init__.py +0 -0
  139. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/facade/__init__.py +0 -0
  140. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/facade/facadeA.py +0 -0
  141. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/factory/__init__.py +0 -0
  142. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/imports.py +0 -0
  143. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/interfaces.py +0 -0
  144. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/example/plugin/reporter/settings.py +0 -0
  145. {module_dependency-1.1.4 → module_dependency-1.1.6}/src/main.py +0 -0
  146. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/__init__.pyi +0 -0
  147. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/__init__.pyi +0 -0
  148. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/__init__.pyi +0 -0
  149. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/base.pyi +0 -0
  150. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/component.pyi +0 -0
  151. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/instance.pyi +0 -0
  152. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/module.pyi +0 -0
  153. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/generation/plugin.pyi +0 -0
  154. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/models/__init__.pyi +0 -0
  155. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/cli/models/base.pyi +0 -0
  156. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/__init__.pyi +0 -0
  157. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/__init__.pyi +0 -0
  158. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/fallback.pyi +0 -0
  159. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/module.pyi +0 -0
  160. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/agrupation/plugin.pyi +0 -0
  161. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/__init__.pyi +0 -0
  162. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/component.pyi +0 -0
  163. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/instance.pyi +0 -0
  164. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/product.pyi +0 -0
  165. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/declaration/validation.pyi +0 -0
  166. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/exceptions.pyi +0 -0
  167. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/__init__.pyi +0 -0
  168. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/injection.pyi +0 -0
  169. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/injection/wiring.pyi +0 -0
  170. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/__init__.pyi +0 -0
  171. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/container.pyi +0 -0
  172. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/errors.pyi +0 -0
  173. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/registry.pyi +0 -0
  174. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/resolver.pyi +0 -0
  175. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/resolution/strategy.pyi +0 -0
  176. {module_dependency-1.1.4 → module_dependency-1.1.6}/stubs/dependency/core/utils/cycle.pyi +0 -0
  177. /module_dependency-1.1.4/src/library/patterns/__init__.py → /module_dependency-1.1.6/stubs/dependency/library/patterns/__init__.pyi +0 -0
  178. {module_dependency-1.1.4 → module_dependency-1.1.6}/tests/cli/test_generation.py +0 -0
  179. {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.4
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.9
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
- # module-dependency
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.Singleton, # Provider type (Singleton, Factory, Resource)
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
- - Declare all the dependencies (components) on Instances and Products to avoid injection issues.
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
- # module-dependency
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:
@@ -40,5 +40,5 @@ nav:
40
40
  - Architecture: ARCHITECTURE.md
41
41
  - API Reference:
42
42
  - Core: reference/core.md
43
- #- CLI: reference/cli.md
43
+ # - CLI: reference/cli.md
44
44
  - Bibliography: REFERENCES.md
@@ -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
- "mkdocs",
41
- "mkdocs-material",
42
- "mkdocstrings[python]",
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.4"
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.9"
79
+ requires-python = ">=3.11"
77
80
  authors = [
78
81
  { name="Fabian D", email="github.clapping767@passmail.net" },
79
82
  ]
@@ -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
@@ -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
 
@@ -70,7 +70,7 @@ def find_cycles(
70
70
  visited.add(node)
71
71
  path.append(node)
72
72
  for dep in function(node):
73
- visit(dep, path, visited)
73
+ visit(dep, path, visited.copy())
74
74
  path.pop()
75
75
 
76
76
  for element in elements:
@@ -0,0 +1,5 @@
1
+ from dependency.library.graph.generate import generate_graph
2
+
3
+ __all__ = [
4
+ "generate_graph",
5
+ ]
@@ -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)