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.
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/CHANGELOG.md +35 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/PKG-INFO +93 -44
- pico_ioc-2.1.0/README.md +242 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/README.md +11 -16
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/README.md +10 -7
- pico_ioc-2.1.0/docs/adr/adr-0002-tree-based-configuration.md +33 -0
- pico_ioc-2.1.0/docs/adr/adr-0010-unified-configuration.md +135 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/container.md +18 -11
- pico_ioc-2.1.0/docs/api-reference/decorators.md +114 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/protocols.md +20 -20
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/comparison.md +13 -12
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/design-principles.md +7 -7
- pico_ioc-2.1.0/docs/architecture/internals.md +207 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-cli-app.md +64 -22
- pico_ioc-2.1.0/docs/observability/exporting-graph.md +113 -0
- pico_ioc-2.1.0/docs/specs/spec-configuration.md +169 -0
- pico_ioc-2.1.0/docs/user-guide/configuration-basic.md +68 -0
- pico_ioc-2.1.0/docs/user-guide/configuration-binding.md +291 -0
- pico_ioc-2.1.0/docs/user-guide/core-concepts.md +249 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/testing.md +2 -2
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/__init__.py +7 -11
- pico_ioc-2.1.0/src/pico_ioc/_version.py +1 -0
- pico_ioc-2.1.0/src/pico_ioc/analysis.py +92 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/aop.py +3 -6
- pico_ioc-2.1.0/src/pico_ioc/api.py +139 -0
- pico_ioc-2.1.0/src/pico_ioc/component_scanner.py +166 -0
- pico_ioc-2.1.0/src/pico_ioc/config_builder.py +91 -0
- pico_ioc-2.1.0/src/pico_ioc/config_registrar.py +219 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/config_runtime.py +17 -2
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/container.py +197 -156
- pico_ioc-2.1.0/src/pico_ioc/decorators.py +192 -0
- pico_ioc-2.1.0/src/pico_ioc/dependency_validator.py +103 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/exceptions.py +0 -16
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/factory.py +2 -1
- pico_ioc-2.1.0/src/pico_ioc/locator.py +131 -0
- pico_ioc-2.1.0/src/pico_ioc/provider_selector.py +35 -0
- pico_ioc-2.1.0/src/pico_ioc/registrar.py +169 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/PKG-INFO +93 -44
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/SOURCES.txt +13 -7
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_configured.py +35 -18
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_pico_extends.py +20 -12
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_pico_integration.py +13 -6
- pico_ioc-2.1.0/tests/test_proxy_unit.py +288 -0
- pico_ioc-2.1.0/tests/test_scope.py +315 -0
- pico_ioc-2.0.4/README.md +0 -193
- pico_ioc-2.0.4/docs/adr/adr-0002-tree-based-configuration.md +0 -31
- pico_ioc-2.0.4/docs/api-reference/decorators.md +0 -95
- pico_ioc-2.0.4/docs/architecture/internals.md +0 -194
- pico_ioc-2.0.4/docs/cookbook/pattern-dynamic-langchain.md +0 -212
- pico_ioc-2.0.4/docs/integrations/ai-langchain.md +0 -293
- pico_ioc-2.0.4/docs/integrations/web-django.md +0 -158
- pico_ioc-2.0.4/docs/integrations/web-fastapi.md +0 -231
- pico_ioc-2.0.4/docs/integrations/web-flask.md +0 -253
- pico_ioc-2.0.4/docs/observability/exporting-graph.md +0 -144
- pico_ioc-2.0.4/docs/user-guide/configuration-basic.md +0 -63
- pico_ioc-2.0.4/docs/user-guide/configuration-binding.md +0 -155
- pico_ioc-2.0.4/docs/user-guide/core-concepts.md +0 -199
- pico_ioc-2.0.4/src/pico_ioc/_version.py +0 -1
- pico_ioc-2.0.4/src/pico_ioc/api.py +0 -1196
- pico_ioc-2.0.4/src/pico_ioc/locator.py +0 -53
- pico_ioc-2.0.4/test.txt +0 -1272
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.coveragerc +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.github/workflows/ci.yml +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/LICENSE +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/MANIFEST.in +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0001-async-native.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0003-context-aware-scopes.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0004-observability.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0005-aop.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0006-eager-validation.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0007-event_bus.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0008-circular-dependencies.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/adr/adr-0009-flexible-provides.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/aop-interceptors.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/async-resolution.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/conditional-binding.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/event-bus.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/advanced-features/health-checks.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/api-reference/glossary.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/architecture/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-feature-toggle.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-profiling.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-security.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-aop-structured-logging.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-cqrs.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-hot-reload.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/cookbook/pattern-multi-tenant.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/getting-started.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/integrations/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/container-context.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/observability/observers-metrics.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/overview.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/README.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/qualifiers-lists.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/docs/user-guide/scopes-lifecycle.md +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/pyproject.toml +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/setup.cfg +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/constants.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/event_bus.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc/scope.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/dependency_links.txt +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/requires.txt +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/src/pico_ioc.egg-info/top_level.txt +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_container_context.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_container_runtime.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_event_bus.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_provides_module_functions.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_provides_static_methods.py +0 -0
- {pico_ioc-2.0.4 → pico_ioc-2.1.0}/tests/test_resolution_graph.py +0 -0
- {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
|
|
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
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
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
|
|
87
|
-
|
|
|
88
|
-
| Object creation| `svc = Service(Repo(Config()))` | `svc = container.get(Service)`
|
|
89
|
-
| Replacing deps
|
|
90
|
-
| Coupling
|
|
91
|
-
| Testing
|
|
92
|
-
| Async support
|
|
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
|
|
96
|
+
## 🧩 Highlights (v2.0+)
|
|
97
97
|
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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) — *
|
|
285
|
+
See [CHANGELOG.md](./CHANGELOG.md) — *Significant redesigns and features in v2.0+.*
|
|
237
286
|
|
|
238
287
|
-----
|
|
239
288
|
|
pico_ioc-2.1.0/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# 📦 Pico-IoC: A Robust, Async-Native IoC Container for Python
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pico-ioc/)
|
|
4
|
+
[](https://deepwiki.com/dperezcabrera/pico-ioc)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+

|
|
7
|
+
[](https://codecov.io/gh/dperezcabrera/pico-ioc)
|
|
8
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
10
|
+
[](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
|
|
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
|
-
|
|
56
|
+
def say_hello(self) -> str: ...
|
|
57
57
|
|
|
58
58
|
@component
|
|
59
59
|
class EnglishGreeter(Greeter):
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
def say_hello(self) -> str:
|
|
61
|
+
return "Hello!"
|
|
62
62
|
|
|
63
63
|
@component
|
|
64
64
|
class App:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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]`).
|