pico-ioc 2.0.4__tar.gz → 2.1.0__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 (115) hide show
  1. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/CHANGELOG.md +35 -0
  2. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/PKG-INFO +93 -44
  3. pico_ioc-2.1.0/README.md +242 -0
  4. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/README.md +11 -16
  5. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/README.md +10 -7
  6. pico_ioc-2.1.0/docs/adr/adr-0002-tree-based-configuration.md +33 -0
  7. pico_ioc-2.1.0/docs/adr/adr-0010-unified-configuration.md +135 -0
  8. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/container.md +18 -11
  9. pico_ioc-2.1.0/docs/api-reference/decorators.md +114 -0
  10. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/protocols.md +20 -20
  11. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/comparison.md +13 -12
  12. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/design-principles.md +7 -7
  13. pico_ioc-2.1.0/docs/architecture/internals.md +207 -0
  14. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-cli-app.md +64 -22
  15. pico_ioc-2.1.0/docs/observability/exporting-graph.md +113 -0
  16. pico_ioc-2.1.0/docs/specs/spec-configuration.md +169 -0
  17. pico_ioc-2.1.0/docs/user-guide/configuration-basic.md +68 -0
  18. pico_ioc-2.1.0/docs/user-guide/configuration-binding.md +291 -0
  19. pico_ioc-2.1.0/docs/user-guide/core-concepts.md +249 -0
  20. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/testing.md +2 -2
  21. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/__init__.py +7 -11
  22. pico_ioc-2.1.0/src/pico_ioc/_version.py +1 -0
  23. pico_ioc-2.1.0/src/pico_ioc/analysis.py +92 -0
  24. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/aop.py +3 -6
  25. pico_ioc-2.1.0/src/pico_ioc/api.py +139 -0
  26. pico_ioc-2.1.0/src/pico_ioc/component_scanner.py +166 -0
  27. pico_ioc-2.1.0/src/pico_ioc/config_builder.py +91 -0
  28. pico_ioc-2.1.0/src/pico_ioc/config_registrar.py +219 -0
  29. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/config_runtime.py +17 -2
  30. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/container.py +197 -156
  31. pico_ioc-2.1.0/src/pico_ioc/decorators.py +192 -0
  32. pico_ioc-2.1.0/src/pico_ioc/dependency_validator.py +103 -0
  33. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/exceptions.py +0 -16
  34. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/factory.py +2 -1
  35. pico_ioc-2.1.0/src/pico_ioc/locator.py +131 -0
  36. pico_ioc-2.1.0/src/pico_ioc/provider_selector.py +35 -0
  37. pico_ioc-2.1.0/src/pico_ioc/registrar.py +169 -0
  38. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/PKG-INFO +93 -44
  39. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/SOURCES.txt +13 -7
  40. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_configured.py +35 -18
  41. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_pico_extends.py +20 -12
  42. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_pico_integration.py +13 -6
  43. pico_ioc-2.1.0/tests/test_proxy_unit.py +288 -0
  44. pico_ioc-2.1.0/tests/test_scope.py +315 -0
  45. pico_ioc-2.0.4/README.md +0 -193
  46. pico_ioc-2.0.4/docs/adr/adr-0002-tree-based-configuration.md +0 -31
  47. pico_ioc-2.0.4/docs/api-reference/decorators.md +0 -95
  48. pico_ioc-2.0.4/docs/architecture/internals.md +0 -194
  49. pico_ioc-2.0.4/docs/cookbook/pattern-dynamic-langchain.md +0 -212
  50. pico_ioc-2.0.4/docs/integrations/ai-langchain.md +0 -293
  51. pico_ioc-2.0.4/docs/integrations/web-django.md +0 -158
  52. pico_ioc-2.0.4/docs/integrations/web-fastapi.md +0 -231
  53. pico_ioc-2.0.4/docs/integrations/web-flask.md +0 -253
  54. pico_ioc-2.0.4/docs/observability/exporting-graph.md +0 -144
  55. pico_ioc-2.0.4/docs/user-guide/configuration-basic.md +0 -63
  56. pico_ioc-2.0.4/docs/user-guide/configuration-binding.md +0 -155
  57. pico_ioc-2.0.4/docs/user-guide/core-concepts.md +0 -199
  58. pico_ioc-2.0.4/src/pico_ioc/_version.py +0 -1
  59. pico_ioc-2.0.4/src/pico_ioc/api.py +0 -1196
  60. pico_ioc-2.0.4/src/pico_ioc/locator.py +0 -53
  61. pico_ioc-2.0.4/test.txt +0 -1272
  62. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.coveragerc +0 -0
  63. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.github/workflows/ci.yml +0 -0
  64. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.github/workflows/publish-to-pypi.yml +0 -0
  65. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/LICENSE +0 -0
  66. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/MANIFEST.in +0 -0
  67. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0001-async-native.md +0 -0
  68. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0003-context-aware-scopes.md +0 -0
  69. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0004-observability.md +0 -0
  70. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0005-aop.md +0 -0
  71. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0006-eager-validation.md +0 -0
  72. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0007-event_bus.md +0 -0
  73. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0008-circular-dependencies.md +0 -0
  74. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0009-flexible-provides.md +0 -0
  75. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/README.md +0 -0
  76. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/aop-interceptors.md +0 -0
  77. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/async-resolution.md +0 -0
  78. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/conditional-binding.md +0 -0
  79. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/event-bus.md +0 -0
  80. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/health-checks.md +0 -0
  81. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/README.md +0 -0
  82. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/glossary.md +0 -0
  83. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/README.md +0 -0
  84. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/README.md +0 -0
  85. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-feature-toggle.md +0 -0
  86. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-profiling.md +0 -0
  87. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-security.md +0 -0
  88. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-structured-logging.md +0 -0
  89. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-cqrs.md +0 -0
  90. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-hot-reload.md +0 -0
  91. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-multi-tenant.md +0 -0
  92. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/getting-started.md +0 -0
  93. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/integrations/README.md +0 -0
  94. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/README.md +0 -0
  95. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/container-context.md +0 -0
  96. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/observers-metrics.md +0 -0
  97. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/overview.md +0 -0
  98. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/README.md +0 -0
  99. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/qualifiers-lists.md +0 -0
  100. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/scopes-lifecycle.md +0 -0
  101. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/pyproject.toml +0 -0
  102. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/setup.cfg +0 -0
  103. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/constants.py +0 -0
  104. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/event_bus.py +0 -0
  105. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/scope.py +0 -0
  106. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/dependency_links.txt +0 -0
  107. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/requires.txt +0 -0
  108. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/top_level.txt +0 -0
  109. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_container_context.py +0 -0
  110. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_container_runtime.py +0 -0
  111. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_event_bus.py +0 -0
  112. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_provides_module_functions.py +0 -0
  113. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_provides_static_methods.py +0 -0
  114. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_resolution_graph.py +0 -0
  115. {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tox.ini +0 -0
@@ -5,6 +5,41 @@ 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.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.html).
7
7
 
8
+
9
+ ---
10
+
11
+ ## [2.1.0] - 2025-10-28
12
+
13
+ ### Added ✨
14
+
15
+ * **Unified Configuration Builder (`configuration(...)`)**: Introduced a new top-level function `configuration(...)` that accepts various sources (`EnvSource`, `DictSource`, `JsonTreeSource`, `YamlTreeSource`, `FlatDictSource`, etc.) and `overrides` to produce an immutable `ContextConfig` object. This centralizes configuration definition and precedence rules (ADR-010).
16
+ * **`ContextConfig` Object**: A new object encapsulating the fully processed configuration state, passed to `init(config=...)`.
17
+ * **Enhanced `@configured` Decorator**:
18
+ * Added `mapping: Literal["auto", "flat", "tree"]` parameter to explicitly control binding strategy.
19
+ * Implemented `"auto"` detection: uses `"tree"` if any field is complex (dataclass, list, dict, Union), otherwise uses `"flat"`.
20
+ * Supports unified normalization rules for keys (e.g., `ENV_VAR_NAME` <-> `field_name`, `ENV_VAR__NESTED` <-> `parent.nested`).
21
+ * Integrates seamlessly with both flat and tree sources managed by `ContextConfig`.
22
+
23
+ ### Changed ⚠️
24
+
25
+ * **`init()` Signature**: The `config` and `tree_config` parameters have been removed. Configuration is now passed solely through the new `config: Optional[ContextConfig]` parameter (ADR-010).
26
+ * **Configuration Precedence**: Configuration loading and precedence are now strictly defined by the order of sources passed to the `configuration(...)` builder, followed by its `overrides` parameter, and finally `Annotated[..., Value(...)]` (ADR-010).
27
+
28
+ ### Removed ❌
29
+
30
+ * **`@configuration` Decorator**: This decorator, previously used for flat key-value binding, has been completely removed in favor of the unified `@configured` decorator (ADR-010).
31
+ * Separate `config` (flat) and `tree_config` arguments in `init()`.
32
+
33
+ ### Documentation 📚
34
+
35
+ * Updated all documentation (User Guide, API Reference, Examples, ADRs) to reflect the unified configuration system based on `@configured`, `configuration(...)`, and `ContextConfig`.
36
+ * Added `docs/specs/spec-configuration.md` detailing the unified configuration rules.
37
+ * Added migration notes related to removing `@configuration`.
38
+
39
+ ### Breaking Changes ⚠️
40
+
41
+ * This release introduces **significant breaking changes** related to configuration management, requiring migration from the old `@configuration` decorator and `init()` arguments to the new `@configured` modes and `configuration(...)` builder (ADR-010).
42
+
8
43
  ---
9
44
 
10
45
  ## [2.0.3] - 2025-10-26
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pico-ioc
3
- Version: 2.0.4
3
+ Version: 2.1.0
4
4
  Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
5
5
  Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
6
6
  License: MIT License
@@ -68,13 +68,13 @@ It brings *Inversion of Control* and *dependency injection* to Python in a deter
68
68
 
69
69
  ## ⚖️ Core Principles
70
70
 
71
- - **Single Purpose** – Do one thing: dependency management.
72
- - **Declarative** – Use simple decorators (`@component`, `@factory`, `@configuration`) instead of config files or YAML magic.
73
- - **Deterministic** – No hidden scanning or side-effects; everything flows from an explicit `init()`.
74
- - **Async-Native** – Fully supports async providers, async lifecycle hooks, and async interceptors.
75
- - **Fail-Fast** – Detects missing bindings and circular dependencies at bootstrap.
76
- - **Testable by Design** – Use `overrides` and `profiles` to swap components instantly.
77
- - **Zero Core Dependencies** – Built entirely on the Python standard library. Optional features may require external packages (see Installation).
71
+ - **Single Purpose** – Do one thing: dependency management.
72
+ - **Declarative** – Use simple decorators (`@component`, `@factory`, `@provides`, `@configured`) instead of complex config files.
73
+ - **Deterministic** – No hidden scanning or side-effects; everything flows from an explicit `init()`.
74
+ - **Async-Native** – Fully supports async providers, async lifecycle hooks (`__ainit__`), and async interceptors.
75
+ - **Fail-Fast** – Detects missing bindings and circular dependencies at bootstrap (`init()`).
76
+ - **Testable by Design** – Use `overrides` and `profiles` to swap components instantly.
77
+ - **Zero Core Dependencies** – Built entirely on the Python standard library. Optional features may require external packages (see Installation).
78
78
 
79
79
  ---
80
80
 
@@ -83,25 +83,24 @@ It brings *Inversion of Control* and *dependency injection* to Python in a deter
83
83
  As Python systems evolve, wiring dependencies by hand becomes fragile and unmaintainable.
84
84
  **Pico-IoC** eliminates that friction by letting you declare how components relate — not how they’re created.
85
85
 
86
- | Feature | Manual Wiring | With Pico-IoC |
87
- | :------------- | :------------------------- | :------------------------------ |
88
- | Object creation| `svc = Service(Repo(Config()))` | `svc = container.get(Service)` |
89
- | Replacing deps | Monkey-patch | `overrides={Repo: FakeRepo()}` |
90
- | Coupling | Tight | Loose |
91
- | Testing | Painful | Instant |
92
- | Async support | Manual | Built-in |
86
+ | Feature | Manual Wiring | With Pico-IoC |
87
+ | :-------------- | :------------------------- | :-------------------------------- |
88
+ | Object creation | `svc = Service(Repo(Config()))` | `svc = container.get(Service)` |
89
+ | Replacing deps | Monkey-patch | `overrides={Repo: FakeRepo()}` |
90
+ | Coupling | Tight | Loose |
91
+ | Testing | Painful | Instant |
92
+ | Async support | Manual | Built-in (`aget`, `__ainit__`, ...) |
93
93
 
94
94
  ---
95
95
 
96
- ## 🧩 Highlights (v2.0.0)
96
+ ## 🧩 Highlights (v2.0+)
97
97
 
98
- - **Full redesign:** unified architecture with simpler, more powerful APIs.
99
- - **Async-aware AOP system** method interceptors via `@intercepted_by`.
100
- - **Typed configuration** dataclasses with JSON/YAML/env sources.
101
- - **Scoped resolution** singleton, prototype, request, session, transaction.
102
- - **UnifiedComponentProxy** transparent lazy/AOP proxy supporting serialization.
103
- - **Tree-based configuration runtime** with reusable adapters and discriminators.
104
- - **Observable container context** with stats, health checks, and async cleanup.
98
+ - **Unified Configuration:** Use `@configured` to bind both **flat** (ENV-like) and **tree** (YAML/JSON) sources via the `configuration(...)` builder (ADR-0010).
99
+ - **Async-aware AOP system:** Method interceptors via `@intercepted_by`.
100
+ - **Scoped resolution:** singleton, prototype, request, session, transaction, and custom scopes.
101
+ - **`UnifiedComponentProxy`:** Transparent `lazy=True` and AOP proxy supporting serialization.
102
+ - **Tree-based configuration runtime:** Advanced mapping with reusable adapters and discriminators (`Annotated[Union[...], Discriminator(...)]`).
103
+ - **Observable container context:** Built-in stats, health checks (`@health`), observer hooks (`ContainerObserver`), dependency graph export (`export_graph`), and async cleanup.
105
104
 
106
105
  ---
107
106
 
@@ -109,7 +108,7 @@ As Python systems evolve, wiring dependencies by hand becomes fragile and unmain
109
108
 
110
109
  ```bash
111
110
  pip install pico-ioc
112
- ```
111
+ ````
113
112
 
114
113
  For optional features, you can install extras:
115
114
 
@@ -121,50 +120,68 @@ For optional features, you can install extras:
121
120
 
122
121
  (Requires `PyYAML`)
123
122
 
124
- * **Dependency Graph Export:**
123
+ * **Dependency Graph Export (Rendering):**
125
124
 
126
125
  ```bash
127
- pip install pico-ioc[graphviz]
126
+ # You still need Graphviz command-line tools installed separately
127
+ # This extra is currently not required by the code,
128
+ # as export_graph generates the .dot file content directly.
129
+ # pip install pico-ioc[graphviz] # Consider removing if not used by code
128
130
  ```
129
131
 
130
- (Requires the `graphviz` Python package and the Graphviz command-line tools)
131
-
132
132
  -----
133
133
 
134
- ## ⚙️ Quick Example
134
+ ## ⚙️ Quick Example (Unified Configuration)
135
135
 
136
136
  ```python
137
+ import os
137
138
  from dataclasses import dataclass
138
- from pico_ioc import component, configuration, init
139
+ from pico_ioc import component, configured, configuration, init, EnvSource
139
140
 
140
- @configuration
141
+ # 1. Define configuration with @configured
142
+ @configured(prefix="APP_", mapping="auto") # Auto-detects flat mapping
141
143
  @dataclass
142
144
  class Config:
143
145
  db_url: str = "sqlite:///demo.db"
144
146
 
147
+ # 2. Define components
145
148
  @component
146
149
  class Repo:
147
- def __init__(self, cfg: Config):
150
+ def __init__(self, cfg: Config): # Inject config
148
151
  self.cfg = cfg
149
152
  def fetch(self):
150
153
  return f"fetching from {self.cfg.db_url}"
151
154
 
152
155
  @component
153
156
  class Service:
154
- def __init__(self, repo: Repo):
157
+ def __init__(self, repo: Repo): # Inject Repo
155
158
  self.repo = repo
156
159
  def run(self):
157
160
  return self.repo.fetch()
158
161
 
159
- container = init(modules=[__name__])
162
+ # --- Example Setup ---
163
+ os.environ['APP_DB_URL'] = 'postgresql://user:pass@host/db'
164
+
165
+ # 3. Build configuration context
166
+ config_ctx = configuration(
167
+ EnvSource(prefix="") # Read APP_DB_URL from environment
168
+ )
169
+
170
+ # 4. Initialize container
171
+ container = init(modules=[__name__], config=config_ctx) # Pass context via 'config'
172
+
173
+ # 5. Get and use the service
160
174
  svc = container.get(Service)
161
175
  print(svc.run())
176
+
177
+ # --- Cleanup ---
178
+ del os.environ['APP_DB_URL']
162
179
  ```
163
180
 
164
181
  **Output:**
165
182
 
166
183
  ```
167
- fetching from sqlite:///demo.db
184
+ fetching from postgresql://user:pass@host/db
168
185
  ```
169
186
 
170
187
  -----
@@ -175,7 +192,16 @@ fetching from sqlite:///demo.db
175
192
  class FakeRepo:
176
193
  def fetch(self): return "fake-data"
177
194
 
178
- container = init(modules=[__name__], overrides={Repo: FakeRepo()})
195
+ # Build configuration context (might be empty or specific for test)
196
+ test_config_ctx = configuration()
197
+
198
+ # Use overrides during init
199
+ container = init(
200
+ modules=[__name__],
201
+ config=test_config_ctx,
202
+ overrides={Repo: FakeRepo()} # Replace Repo with FakeRepo
203
+ )
204
+
179
205
  svc = container.get(Service)
180
206
  assert svc.run() == "fake-data"
181
207
  ```
@@ -185,23 +211,46 @@ assert svc.run() == "fake-data"
185
211
  ## 🩺 Lifecycle & AOP
186
212
 
187
213
  ```python
188
- from pico_ioc import intercepted_by, MethodInterceptor, MethodCtx
214
+ import time # For example
215
+ from pico_ioc import component, init, intercepted_by, MethodInterceptor, MethodCtx
189
216
 
217
+ # Define an interceptor component
218
+ @component
190
219
  class LogInterceptor(MethodInterceptor):
191
220
  def invoke(self, ctx: MethodCtx, call_next):
192
- print(f"→ calling {ctx.name}")
193
- res = call_next(ctx)
194
- print(f"← {ctx.name} done")
195
- return res
221
+ print(f"→ calling {ctx.cls.__name__}.{ctx.name}")
222
+ start = time.perf_counter()
223
+ try:
224
+ res = call_next(ctx)
225
+ duration = (time.perf_counter() - start) * 1000
226
+ print(f"← {ctx.cls.__name__}.{ctx.name} done ({duration:.2f}ms)")
227
+ return res
228
+ except Exception as e:
229
+ duration = (time.perf_counter() - start) * 1000
230
+ print(f"← {ctx.cls.__name__}.{ctx.name} failed ({duration:.2f}ms): {e}")
231
+ raise
196
232
 
197
233
  @component
198
234
  class Demo:
199
- @intercepted_by(LogInterceptor)
235
+ @intercepted_by(LogInterceptor) # Apply the interceptor
200
236
  def work(self):
237
+ print(" Working...")
238
+ time.sleep(0.01)
201
239
  return "ok"
202
240
 
241
+ # Initialize container (must scan module containing interceptor too)
203
242
  c = init(modules=[__name__])
204
- c.get(Demo).work()
243
+ result = c.get(Demo).work()
244
+ print(f"Result: {result}")
245
+ ```
246
+
247
+ **Output:**
248
+
249
+ ```
250
+ → calling Demo.work
251
+ Working...
252
+ ← Demo.work done (10.xxms)
253
+ Result: ok
205
254
  ```
206
255
 
207
256
  -----
@@ -233,7 +282,7 @@ tox
233
282
 
234
283
  ## 🧾 Changelog
235
284
 
236
- See [CHANGELOG.md](./CHANGELOG.md) — *Full redesign for v2.0.0.*
285
+ See [CHANGELOG.md](./CHANGELOG.md) — *Significant redesigns and features in v2.0+.*
237
286
 
238
287
  -----
239
288
 
@@ -0,0 +1,242 @@
1
+ # 📦 Pico-IoC: A Robust, Async-Native IoC Container for Python
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pico-ioc.svg)](https://pypi.org/project/pico-ioc/)
4
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/dperezcabrera/pico-ioc)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ ![CI (tox matrix)](https://github.com/dperezcabrera/pico-ioc/actions/workflows/ci.yml/badge.svg)
7
+ [![codecov](https://codecov.io/gh/dperezcabrera/pico-ioc/branch/main/graph/badge.svg)](https://codecov.io/gh/dperezcabrera/pico-ioc)
8
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dperezcabrera_pico-ioc&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
9
+ [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=dperezcabrera_pico-ioc&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
10
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=dperezcabrera_pico-ioc&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
11
+
12
+ **Pico-IoC** is a **lightweight, async-ready, decorator-driven IoC container** built for clarity, testability, and performance.
13
+ It brings *Inversion of Control* and *dependency injection* to Python in a deterministic, modern, and framework-agnostic way.
14
+
15
+ > 🐍 Requires **Python 3.10+**
16
+
17
+ ---
18
+
19
+ ## ⚖️ Core Principles
20
+
21
+ - **Single Purpose** – Do one thing: dependency management.
22
+ - **Declarative** – Use simple decorators (`@component`, `@factory`, `@provides`, `@configured`) instead of complex config files.
23
+ - **Deterministic** – No hidden scanning or side-effects; everything flows from an explicit `init()`.
24
+ - **Async-Native** – Fully supports async providers, async lifecycle hooks (`__ainit__`), and async interceptors.
25
+ - **Fail-Fast** – Detects missing bindings and circular dependencies at bootstrap (`init()`).
26
+ - **Testable by Design** – Use `overrides` and `profiles` to swap components instantly.
27
+ - **Zero Core Dependencies** – Built entirely on the Python standard library. Optional features may require external packages (see Installation).
28
+
29
+ ---
30
+
31
+ ## 🚀 Why Pico-IoC?
32
+
33
+ As Python systems evolve, wiring dependencies by hand becomes fragile and unmaintainable.
34
+ **Pico-IoC** eliminates that friction by letting you declare how components relate — not how they’re created.
35
+
36
+ | Feature | Manual Wiring | With Pico-IoC |
37
+ | :-------------- | :------------------------- | :-------------------------------- |
38
+ | Object creation | `svc = Service(Repo(Config()))` | `svc = container.get(Service)` |
39
+ | Replacing deps | Monkey-patch | `overrides={Repo: FakeRepo()}` |
40
+ | Coupling | Tight | Loose |
41
+ | Testing | Painful | Instant |
42
+ | Async support | Manual | Built-in (`aget`, `__ainit__`, ...) |
43
+
44
+ ---
45
+
46
+ ## 🧩 Highlights (v2.0+)
47
+
48
+ - **Unified Configuration:** Use `@configured` to bind both **flat** (ENV-like) and **tree** (YAML/JSON) sources via the `configuration(...)` builder (ADR-0010).
49
+ - **Async-aware AOP system:** Method interceptors via `@intercepted_by`.
50
+ - **Scoped resolution:** singleton, prototype, request, session, transaction, and custom scopes.
51
+ - **`UnifiedComponentProxy`:** Transparent `lazy=True` and AOP proxy supporting serialization.
52
+ - **Tree-based configuration runtime:** Advanced mapping with reusable adapters and discriminators (`Annotated[Union[...], Discriminator(...)]`).
53
+ - **Observable container context:** Built-in stats, health checks (`@health`), observer hooks (`ContainerObserver`), dependency graph export (`export_graph`), and async cleanup.
54
+
55
+ ---
56
+
57
+ ## 📦 Installation
58
+
59
+ ```bash
60
+ pip install pico-ioc
61
+ ````
62
+
63
+ For optional features, you can install extras:
64
+
65
+ * **YAML Configuration:**
66
+
67
+ ```bash
68
+ pip install pico-ioc[yaml]
69
+ ```
70
+
71
+ (Requires `PyYAML`)
72
+
73
+ * **Dependency Graph Export (Rendering):**
74
+
75
+ ```bash
76
+ # You still need Graphviz command-line tools installed separately
77
+ # This extra is currently not required by the code,
78
+ # as export_graph generates the .dot file content directly.
79
+ # pip install pico-ioc[graphviz] # Consider removing if not used by code
80
+ ```
81
+
82
+ -----
83
+
84
+ ## ⚙️ Quick Example (Unified Configuration)
85
+
86
+ ```python
87
+ import os
88
+ from dataclasses import dataclass
89
+ from pico_ioc import component, configured, configuration, init, EnvSource
90
+
91
+ # 1. Define configuration with @configured
92
+ @configured(prefix="APP_", mapping="auto") # Auto-detects flat mapping
93
+ @dataclass
94
+ class Config:
95
+ db_url: str = "sqlite:///demo.db"
96
+
97
+ # 2. Define components
98
+ @component
99
+ class Repo:
100
+ def __init__(self, cfg: Config): # Inject config
101
+ self.cfg = cfg
102
+ def fetch(self):
103
+ return f"fetching from {self.cfg.db_url}"
104
+
105
+ @component
106
+ class Service:
107
+ def __init__(self, repo: Repo): # Inject Repo
108
+ self.repo = repo
109
+ def run(self):
110
+ return self.repo.fetch()
111
+
112
+ # --- Example Setup ---
113
+ os.environ['APP_DB_URL'] = 'postgresql://user:pass@host/db'
114
+
115
+ # 3. Build configuration context
116
+ config_ctx = configuration(
117
+ EnvSource(prefix="") # Read APP_DB_URL from environment
118
+ )
119
+
120
+ # 4. Initialize container
121
+ container = init(modules=[__name__], config=config_ctx) # Pass context via 'config'
122
+
123
+ # 5. Get and use the service
124
+ svc = container.get(Service)
125
+ print(svc.run())
126
+
127
+ # --- Cleanup ---
128
+ del os.environ['APP_DB_URL']
129
+ ```
130
+
131
+ **Output:**
132
+
133
+ ```
134
+ fetching from postgresql://user:pass@host/db
135
+ ```
136
+
137
+ -----
138
+
139
+ ## 🧪 Testing with Overrides
140
+
141
+ ```python
142
+ class FakeRepo:
143
+ def fetch(self): return "fake-data"
144
+
145
+ # Build configuration context (might be empty or specific for test)
146
+ test_config_ctx = configuration()
147
+
148
+ # Use overrides during init
149
+ container = init(
150
+ modules=[__name__],
151
+ config=test_config_ctx,
152
+ overrides={Repo: FakeRepo()} # Replace Repo with FakeRepo
153
+ )
154
+
155
+ svc = container.get(Service)
156
+ assert svc.run() == "fake-data"
157
+ ```
158
+
159
+ -----
160
+
161
+ ## 🩺 Lifecycle & AOP
162
+
163
+ ```python
164
+ import time # For example
165
+ from pico_ioc import component, init, intercepted_by, MethodInterceptor, MethodCtx
166
+
167
+ # Define an interceptor component
168
+ @component
169
+ class LogInterceptor(MethodInterceptor):
170
+ def invoke(self, ctx: MethodCtx, call_next):
171
+ print(f"→ calling {ctx.cls.__name__}.{ctx.name}")
172
+ start = time.perf_counter()
173
+ try:
174
+ res = call_next(ctx)
175
+ duration = (time.perf_counter() - start) * 1000
176
+ print(f"← {ctx.cls.__name__}.{ctx.name} done ({duration:.2f}ms)")
177
+ return res
178
+ except Exception as e:
179
+ duration = (time.perf_counter() - start) * 1000
180
+ print(f"← {ctx.cls.__name__}.{ctx.name} failed ({duration:.2f}ms): {e}")
181
+ raise
182
+
183
+ @component
184
+ class Demo:
185
+ @intercepted_by(LogInterceptor) # Apply the interceptor
186
+ def work(self):
187
+ print(" Working...")
188
+ time.sleep(0.01)
189
+ return "ok"
190
+
191
+ # Initialize container (must scan module containing interceptor too)
192
+ c = init(modules=[__name__])
193
+ result = c.get(Demo).work()
194
+ print(f"Result: {result}")
195
+ ```
196
+
197
+ **Output:**
198
+
199
+ ```
200
+ → calling Demo.work
201
+ Working...
202
+ ← Demo.work done (10.xxms)
203
+ Result: ok
204
+ ```
205
+
206
+ -----
207
+
208
+ ## 📖 Documentation
209
+
210
+ The full documentation is available within the `docs/` directory of the project repository. Start with `docs/README.md` for navigation.
211
+
212
+ * **Getting Started:** `docs/getting-started.md`
213
+ * **User Guide:** `docs/user-guide/README.md`
214
+ * **Advanced Features:** `docs/advanced-features/README.md`
215
+ * **Observability:** `docs/observability/README.md`
216
+ * **Integrations:** `docs/integrations/README.md`
217
+ * **Cookbook (Patterns):** `docs/cookbook/README.md`
218
+ * **Architecture:** `docs/architecture/README.md`
219
+ * **API Reference:** `docs/api-reference/README.md`
220
+ * **ADR Index:** `docs/adr/README.md`
221
+
222
+ -----
223
+
224
+ ## 🧩 Development
225
+
226
+ ```bash
227
+ pip install tox
228
+ tox
229
+ ```
230
+
231
+ -----
232
+
233
+ ## 🧾 Changelog
234
+
235
+ See [CHANGELOG.md](./CHANGELOG.md) — *Significant redesigns and features in v2.0+.*
236
+
237
+ -----
238
+
239
+ ## 📜 License
240
+
241
+ MIT — [LICENSE](https://opensource.org/licenses/MIT)
242
+
@@ -7,7 +7,7 @@ This documentation site guides you from your first component to building complex
7
7
  ## Key Features
8
8
 
9
9
  * 🚀 **Async-Native:** Full support for `async`/`await` in component resolution (`aget`), lifecycle methods (`__ainit__`, `@cleanup`), AOP interceptors, and the Event Bus.
10
- * 🌳 **Advanced Tree-Binding:** Use `@configured` to map complex YAML/JSON configuration trees directly to `dataclass` graphs, including support for `Union` types and custom discriminators.
10
+ * 🌳 **Advanced Unified Configuration:** Use `@configured` to map complex YAML/JSON configuration trees *or* flat key-value sources (like ENV) directly to `dataclass` graphs via the `configuration(...)` builder, with clear precedence rules and normalization. # <-- Updated this point slightly for ADR-0010
11
11
  * 🔬 **Observability-First:** Built-in container contexts (`as_current`), stats (`.stats()`), and observer protocols (`ContainerObserver`) to monitor, trace, and debug your application's components.
12
12
  * ✨ **Powerful AOP:** Intercept method calls for cross-cutting concerns (like logging, tracing, or caching) using `@intercepted_by` without modifying your business logic.
13
13
  * ✅ **Fail-Fast Validation:** The container validates all component dependencies at startup (`init()`), preventing `ProviderNotFoundError` exceptions at runtime.
@@ -53,24 +53,25 @@ from pico_ioc import component, init
53
53
 
54
54
  # 1. Define your components
55
55
  class Greeter:
56
-     def say_hello(self) -> str: ...
56
+ def say_hello(self) -> str: ...
57
57
 
58
58
  @component
59
59
  class EnglishGreeter(Greeter):
60
-     def say_hello(self) -> str:
61
-         return "Hello!"
60
+ def say_hello(self) -> str:
61
+ return "Hello!"
62
62
 
63
63
  @component
64
64
  class App:
65
-     # 2. Declare dependencies in the constructor
66
-     def __init__(self, greeter: Greeter):
67
-         self.greeter = greeter
68
-     
69
-     def run(self):
70
-         print(self.greeter.say_hello())
65
+ # 2. Declare dependencies in the constructor
66
+ def __init__(self, greeter: Greeter):
67
+ self.greeter = greeter
68
+
69
+ def run(self):
70
+ print(self.greeter.say_hello())
71
71
 
72
72
  # 3. Initialize the container
73
73
  # The 'modules' list tells pico_ioc where to scan for @component
74
+ # This example doesn't need explicit configuration sources.
74
75
  container = init(modules=[__name__])
75
76
 
76
77
  # 4. Get the root component and run
@@ -80,10 +81,4 @@ app.run()
80
81
  # Output: Hello!
81
82
  ```
82
83
 
83
- -----
84
-
85
- ## Navigation
86
-
87
- | [⬅️ Anterior: Inicio](./README.md) | [🏠 Índice Principal](./README.md) | [Siguiente ➡️: Guía de Usuario](./user-guide/README.md) |
88
- | :--- | :--- | :--- |
89
84
 
@@ -1,15 +1,18 @@
1
+ Okay, here's the corrected index list for the Architecture Decision Records (ADRs):
2
+
1
3
  # Architecture Decision Records (ADRs)
2
4
 
3
5
  This index lists all significant architecture decisions for the `pico-ioc` project. Keep it sorted by ADR number.
4
6
 
5
7
  ---
6
8
 
7
- * [ADR-001: Native Asyncio Support](./adr-0001-async-native.md) — Accepted
8
- * [ADR-002: Tree-Based Configuration](./adr-0002-tree-based-configuration.md) — Accepted
9
- * [ADR-003: Context-Aware Scopes](./adr-0003-context-aware-scopes.md) — Accepted
10
- * [ADR-004: Observability Features](./adr-0004-observability.md) — Accepted
11
- * [ADR-005: Aspect-Oriented Programming (AOP)](./adr-0005-aop.md) — Accepted
12
- * [ADR-006: Eager Validation](./adr-0006-eager-validation.md) — Accepted
13
- * [ADR-007: Built-in Asynchronous Event Bus](./adr-0007-event_bus.md) — Accepted
9
+ * [ADR-001: Native Asyncio Support](./adr-0001-async-native.md) — Accepted
10
+ * [ADR-002: Tree-Based Configuration](./adr-0002-tree-based-configuration.md) — Accepted (Partially Superseded by ADR-010)
11
+ * [ADR-003: Context-Aware Scopes](./adr-0003-context-aware-scopes.md) — Accepted
12
+ * [ADR-004: Observability Features](./adr-0004-observability.md) — Accepted
13
+ * [ADR-005: Aspect-Oriented Programming (AOP)](./adr-0005-aop.md) — Accepted
14
+ * [ADR-006: Eager Validation](./adr-0006-eager-validation.md) — Accepted
15
+ * [ADR-007: Built-in Asynchronous Event Bus](./adr-0007-event_bus.md) — Accepted
14
16
  * [ADR-008: Explicit Handling of Circular Dependencies](./adr-0008-circular-dependencies.md) — Accepted
15
17
  * [ADR-009: Flexible @provides for Static and Module-level Functions](./adr-0009-flexible-provides.md) — Accepted
18
+ * [ADR-010: Unified Configuration via `@configured` and `ContextConfig`](./adr-0010-unified-configuration.md) — Accepted
@@ -0,0 +1,33 @@
1
+ # ADR-002: Tree-Based Configuration Binding
2
+
3
+ **Status:** Accepted (Partially Superseded by [ADR-0010](./adr-0010-unified-configuration.md))
4
+
5
+ > **Note:** While the core concepts of **tree-binding logic** (`ConfigResolver`, `ObjectGraphBuilder`) and using `@configured` for nested structures remain valid, **ADR-0010 unified the configuration system**. The mechanism described here using a separate `init(tree_config=...)` argument is **no longer current**. Configuration sources (including tree sources like `YamlTreeSource`) are now passed to the `configuration(...)` builder, and the resulting `ContextConfig` object is passed to `init(config=...)`. The `@configured` decorator now handles both flat and tree mapping via its `mapping` parameter.
6
+
7
+ ## Context
8
+
9
+ Basic configuration (`@configuration` with `ConfigSource` - *now removed*) was suitable for flat key-value pairs but became cumbersome for complex, nested application settings common in modern microservices (e.g., configuring databases, caches, feature flags, external clients with nested properties). Manually parsing nested structures or using complex prefixes was error-prone and lacked type safety beyond simple primitives. We needed a way to map structured configuration files (like YAML or JSON) directly to Python object graphs (like `dataclasses`).
10
+
11
+ ## Decision
12
+
13
+ We introduced a **dedicated tree-binding system**:
14
+
15
+ 1. **`TreeSource` Protocol:** Defined sources that provide configuration as a nested `Mapping` (e.g., `YamlTreeSource`, `JsonTreeSource`). These are now passed to the **`configuration(...)` builder** *(updated per ADR-0010)*.
16
+ 2. **`ConfigResolver`:** An internal component that loads, merges (sources are layered according to `configuration(...)` order), and interpolates (`${ENV:VAR}`, `${ref:path}`) all `TreeSource`s into a single, final configuration tree.
17
+ 3. **`ObjectGraphBuilder`:** An internal component that recursively maps a sub-tree (selected by a `prefix`) from the `ConfigResolver` onto a target Python type (usually a `dataclass`). It handles type coercion, nested objects, lists, dictionaries, `Union`s (with `Discriminator`), and `Enum`s.
18
+ 4. **`@configured(prefix="key", mapping="tree"|"auto")` Decorator:** *(Updated per ADR-0010)* A registration mechanism that tells `pico-ioc` to create a provider for the target type by using the `ObjectGraphBuilder` to map the configuration sub-tree found at `prefix`, when the `mapping` is determined to be `"tree"` (either explicitly or via `"auto"` detection).
19
+
20
+ ## Consequences
21
+
22
+ **Positive:** 👍
23
+ * Enables highly structured, type-safe configuration.
24
+ * Configuration structure directly mirrors `dataclass` definitions, improving clarity.
25
+ * Supports common formats like YAML and JSON naturally.
26
+ * Interpolation allows for dynamic values and avoids repetition.
27
+ * Decouples components from the *source* of configuration (env, file, etc.).
28
+ * Polymorphic configuration (`Union` + `Discriminator`) allows for flexible setup (e.g., selecting different cache backends via config).
29
+
30
+ **Negative:** 👎
31
+ * *(Original negative points about having two systems are mostly resolved by ADR-0010)*
32
+ * Requires understanding the mapping rules (prefix, type coercion, discriminators, `mapping` parameter).
33
+ * Adds optional dependencies for formats like YAML (`pip install pico-ioc[yaml]`).