pico-ioc 2.1.1__py3-none-any.whl → 2.1.2__py3-none-any.whl
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/_version.py +1 -1
- pico_ioc/analysis.py +47 -4
- pico_ioc/container.py +49 -6
- pico_ioc/locator.py +9 -1
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.2.dist-info}/METADATA +122 -62
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.2.dist-info}/RECORD +9 -9
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.2.dist-info}/WHEEL +0 -0
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.2.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.2.dist-info}/top_level.txt +0 -0
pico_ioc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.1.
|
|
1
|
+
__version__ = '2.1.2'
|
pico_ioc/analysis.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
|
|
3
|
+
import collections
|
|
4
|
+
import collections.abc
|
|
5
|
+
from typing import (
|
|
6
|
+
Any, Callable, List, Optional, Tuple, Union, get_args, get_origin, Annotated,
|
|
7
|
+
Iterable, Set, Sequence, Collection, Deque, FrozenSet, MutableSequence, MutableSet,
|
|
8
|
+
Dict, Mapping
|
|
9
|
+
)
|
|
4
10
|
from .decorators import Qualifier
|
|
5
11
|
|
|
6
12
|
KeyT = Union[str, type]
|
|
@@ -12,6 +18,8 @@ class DependencyRequest:
|
|
|
12
18
|
is_list: bool = False
|
|
13
19
|
qualifier: Optional[str] = None
|
|
14
20
|
is_optional: bool = False
|
|
21
|
+
is_dict: bool = False
|
|
22
|
+
dict_key_type: Any = None
|
|
15
23
|
|
|
16
24
|
def _extract_annotated(ann: Any) -> Tuple[Any, Optional[str]]:
|
|
17
25
|
qualifier = None
|
|
@@ -44,6 +52,24 @@ def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[Dep
|
|
|
44
52
|
|
|
45
53
|
plan: List[DependencyRequest] = []
|
|
46
54
|
|
|
55
|
+
SUPPORTED_COLLECTION_ORIGINS = (
|
|
56
|
+
# Runtime types
|
|
57
|
+
list,
|
|
58
|
+
set,
|
|
59
|
+
tuple,
|
|
60
|
+
frozenset,
|
|
61
|
+
collections.deque,
|
|
62
|
+
|
|
63
|
+
# Typing ABCs (from get_origin)
|
|
64
|
+
collections.abc.Iterable,
|
|
65
|
+
collections.abc.Collection,
|
|
66
|
+
collections.abc.Sequence,
|
|
67
|
+
collections.abc.MutableSequence,
|
|
68
|
+
collections.abc.MutableSet
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
SUPPORTED_DICT_ORIGINS = (dict, collections.abc.Mapping)
|
|
72
|
+
|
|
47
73
|
for name, param in sig.parameters.items():
|
|
48
74
|
if name in ("self", "cls"):
|
|
49
75
|
continue
|
|
@@ -56,19 +82,35 @@ def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[Dep
|
|
|
56
82
|
base_type, qualifier = _extract_annotated(base_type)
|
|
57
83
|
|
|
58
84
|
is_list = False
|
|
85
|
+
is_dict = False
|
|
59
86
|
elem_t = None
|
|
87
|
+
dict_key_t = None
|
|
60
88
|
|
|
61
89
|
origin = get_origin(base_type)
|
|
62
|
-
|
|
90
|
+
|
|
91
|
+
if origin in SUPPORTED_COLLECTION_ORIGINS:
|
|
63
92
|
is_list = True
|
|
64
93
|
elem_t = get_args(base_type)[0] if get_args(base_type) else Any
|
|
65
94
|
elem_t, list_qualifier = _extract_annotated(elem_t)
|
|
66
95
|
if qualifier is None:
|
|
67
96
|
qualifier = list_qualifier
|
|
97
|
+
elif origin in SUPPORTED_DICT_ORIGINS:
|
|
98
|
+
is_dict = True
|
|
99
|
+
args = get_args(base_type)
|
|
100
|
+
dict_key_t = args[0] if args else Any
|
|
101
|
+
elem_t = args[1] if len(args) > 1 else Any
|
|
102
|
+
elem_t, dict_qualifier = _extract_annotated(elem_t)
|
|
103
|
+
if qualifier is None:
|
|
104
|
+
qualifier = dict_qualifier
|
|
68
105
|
|
|
69
106
|
final_key: KeyT
|
|
107
|
+
final_dict_key_type: Any = None
|
|
108
|
+
|
|
70
109
|
if is_list:
|
|
71
110
|
final_key = elem_t if isinstance(elem_t, type) else Any
|
|
111
|
+
elif is_dict:
|
|
112
|
+
final_key = elem_t if isinstance(elem_t, type) else Any
|
|
113
|
+
final_dict_key_type = dict_key_t
|
|
72
114
|
elif isinstance(base_type, type):
|
|
73
115
|
final_key = base_type
|
|
74
116
|
elif isinstance(base_type, str):
|
|
@@ -84,9 +126,10 @@ def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[Dep
|
|
|
84
126
|
key=final_key,
|
|
85
127
|
is_list=is_list,
|
|
86
128
|
qualifier=qualifier,
|
|
87
|
-
is_optional=is_optional or (param.default is not inspect._empty)
|
|
129
|
+
is_optional=is_optional or (param.default is not inspect._empty),
|
|
130
|
+
is_dict=is_dict,
|
|
131
|
+
dict_key_type=final_dict_key_type
|
|
88
132
|
)
|
|
89
133
|
)
|
|
90
134
|
|
|
91
135
|
return tuple(plan)
|
|
92
|
-
|
pico_ioc/container.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# src/pico_ioc/container.py
|
|
2
|
-
|
|
3
1
|
import inspect
|
|
4
2
|
import contextvars
|
|
5
3
|
import functools
|
|
6
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Any, Dict, List, Optional, Tuple, overload, Union, Callable,
|
|
6
|
+
Iterable, Set, get_args, get_origin, Annotated, Protocol, Mapping, Type
|
|
7
|
+
)
|
|
7
8
|
from contextlib import contextmanager
|
|
8
9
|
from .constants import LOGGER, PICO_META
|
|
9
10
|
from .exceptions import ComponentCreationError, ProviderNotFoundError, AsyncResolutionError, ConfigurationError
|
|
@@ -433,19 +434,61 @@ class PicoContainer:
|
|
|
433
434
|
|
|
434
435
|
def _resolve_args(self, dependencies: Tuple[DependencyRequest, ...]) -> Dict[str, Any]:
|
|
435
436
|
kwargs: Dict[str, Any] = {}
|
|
436
|
-
if not dependencies:
|
|
437
|
+
if not dependencies or self._locator is None:
|
|
437
438
|
return kwargs
|
|
438
439
|
|
|
439
440
|
for dep in dependencies:
|
|
440
441
|
if dep.is_list:
|
|
441
442
|
keys: Tuple[KeyT, ...] = ()
|
|
442
|
-
if
|
|
443
|
+
if isinstance(dep.key, type):
|
|
443
444
|
keys = tuple(self._locator.collect_by_type(dep.key, dep.qualifier))
|
|
444
445
|
kwargs[dep.parameter_name] = [self.get(k) for k in keys]
|
|
445
446
|
continue
|
|
447
|
+
|
|
448
|
+
if dep.is_dict:
|
|
449
|
+
value_type = dep.key
|
|
450
|
+
key_type = dep.dict_key_type
|
|
451
|
+
result_map: Dict[Any, Any] = {}
|
|
452
|
+
|
|
453
|
+
keys_to_resolve: Tuple[KeyT, ...] = ()
|
|
454
|
+
if isinstance(value_type, type):
|
|
455
|
+
keys_to_resolve = tuple(self._locator.collect_by_type(value_type, dep.qualifier))
|
|
456
|
+
|
|
457
|
+
for comp_key in keys_to_resolve:
|
|
458
|
+
instance = self.get(comp_key)
|
|
459
|
+
md = self._locator._metadata.get(comp_key)
|
|
460
|
+
if md is None:
|
|
461
|
+
continue
|
|
462
|
+
|
|
463
|
+
dict_key: Any = None
|
|
464
|
+
if key_type is str:
|
|
465
|
+
dict_key = md.pico_name
|
|
466
|
+
if dict_key is None:
|
|
467
|
+
if isinstance(comp_key, str):
|
|
468
|
+
dict_key = comp_key
|
|
469
|
+
else:
|
|
470
|
+
dict_key = getattr(comp_key, "__name__", str(comp_key))
|
|
471
|
+
elif key_type is type or key_type is Type:
|
|
472
|
+
dict_key = md.concrete_class or md.provided_type
|
|
473
|
+
elif key_type is Any:
|
|
474
|
+
dict_key = md.pico_name
|
|
475
|
+
if dict_key is None:
|
|
476
|
+
if isinstance(comp_key, str):
|
|
477
|
+
dict_key = comp_key
|
|
478
|
+
else:
|
|
479
|
+
dict_key = getattr(comp_key, "__name__", str(comp_key))
|
|
480
|
+
|
|
481
|
+
if dict_key is not None:
|
|
482
|
+
if (key_type is type or key_type is Type) and not isinstance(dict_key, type):
|
|
483
|
+
continue
|
|
484
|
+
|
|
485
|
+
result_map[dict_key] = instance
|
|
486
|
+
|
|
487
|
+
kwargs[dep.parameter_name] = result_map
|
|
488
|
+
continue
|
|
446
489
|
|
|
447
490
|
primary_key = dep.key
|
|
448
|
-
if isinstance(primary_key, str)
|
|
491
|
+
if isinstance(primary_key, str):
|
|
449
492
|
mapped = self._locator.find_key_by_name(primary_key)
|
|
450
493
|
primary_key = mapped if mapped is not None else primary_key
|
|
451
494
|
|
pico_ioc/locator.py
CHANGED
|
@@ -80,9 +80,13 @@ class ComponentLocator:
|
|
|
80
80
|
return isinstance(inst, proto)
|
|
81
81
|
except Exception:
|
|
82
82
|
pass
|
|
83
|
+
|
|
83
84
|
for name, val in proto.__dict__.items():
|
|
84
|
-
if name.startswith("_") or not callable(val):
|
|
85
|
+
if name.startswith("_") or not (callable(val) or name in getattr(proto, "__annotations__", {})):
|
|
85
86
|
continue
|
|
87
|
+
|
|
88
|
+
if not hasattr(typ, name):
|
|
89
|
+
return False
|
|
86
90
|
return True
|
|
87
91
|
|
|
88
92
|
def collect_by_type(self, t: type, q: Optional[str]) -> List[KeyT]:
|
|
@@ -122,6 +126,10 @@ class ComponentLocator:
|
|
|
122
126
|
if isinstance(dep.key, type):
|
|
123
127
|
keys = self.collect_by_type(dep.key, dep.qualifier)
|
|
124
128
|
deps.extend(keys)
|
|
129
|
+
elif dep.is_dict:
|
|
130
|
+
if isinstance(dep.key, type):
|
|
131
|
+
keys = self.collect_by_type(dep.key, dep.qualifier)
|
|
132
|
+
deps.extend(keys)
|
|
125
133
|
else:
|
|
126
134
|
deps.append(dep.key)
|
|
127
135
|
return tuple(deps)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pico-ioc
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
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
|
|
@@ -58,49 +58,53 @@ Dynamic: license-file
|
|
|
58
58
|
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
59
59
|
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
60
60
|
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
61
|
+
[](https://pepy.tech/projects/pico-ioc)
|
|
62
|
+
[](https://dperezcabrera.github.io/pico-ioc/)
|
|
63
|
+
[](https://dperezcabrera.github.io/learn-pico-ioc/)
|
|
64
|
+
|
|
61
65
|
|
|
62
66
|
**Pico-IoC** is a **lightweight, async-ready, decorator-driven IoC container** built for clarity, testability, and performance.
|
|
63
|
-
It brings
|
|
67
|
+
It brings Inversion of Control and dependency injection to Python in a deterministic, modern, and framework-agnostic way.
|
|
64
68
|
|
|
65
|
-
> 🐍 Requires
|
|
69
|
+
> 🐍 Requires Python 3.10+
|
|
66
70
|
|
|
67
71
|
---
|
|
68
72
|
|
|
69
73
|
## ⚖️ Core Principles
|
|
70
74
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
75
|
+
- Single Purpose – Do one thing: dependency management.
|
|
76
|
+
- Declarative – Use simple decorators (`@component`, `@factory`, `@provides`, `@configured`) instead of complex config files.
|
|
77
|
+
- Deterministic – No hidden scanning or side-effects; everything flows from an explicit `init()`.
|
|
78
|
+
- Async-Native – Fully supports async providers, async lifecycle hooks (`__ainit__`), and async interceptors.
|
|
79
|
+
- Fail-Fast – Detects missing bindings and circular dependencies at bootstrap (`init()`).
|
|
80
|
+
- Testable by Design – Use `overrides` and `profiles` to swap components instantly.
|
|
81
|
+
- Zero Core Dependencies – Built entirely on the Python standard library. Optional features may require external packages (see Installation).
|
|
78
82
|
|
|
79
83
|
---
|
|
80
84
|
|
|
81
85
|
## 🚀 Why Pico-IoC?
|
|
82
86
|
|
|
83
87
|
As Python systems evolve, wiring dependencies by hand becomes fragile and unmaintainable.
|
|
84
|
-
|
|
88
|
+
Pico-IoC eliminates that friction by letting you declare how components relate — not how they’re created.
|
|
85
89
|
|
|
86
|
-
| Feature | Manual Wiring
|
|
87
|
-
| :-------------- |
|
|
88
|
-
| Object creation | `svc = Service(Repo(Config()))` | `svc = container.get(Service)`
|
|
89
|
-
| Replacing deps | Monkey-patch
|
|
90
|
-
| Coupling | Tight
|
|
91
|
-
| Testing | Painful
|
|
92
|
-
| Async support | Manual
|
|
90
|
+
| Feature | Manual Wiring | With Pico-IoC |
|
|
91
|
+
| :-------------- | :----------------------------- | :------------------------------ |
|
|
92
|
+
| Object creation | `svc = Service(Repo(Config()))` | `svc = container.get(Service)` |
|
|
93
|
+
| Replacing deps | Monkey-patch | `overrides={Repo: FakeRepo()}` |
|
|
94
|
+
| Coupling | Tight | Loose |
|
|
95
|
+
| Testing | Painful | Instant |
|
|
96
|
+
| Async support | Manual | Built-in (`aget`, `__ainit__`) |
|
|
93
97
|
|
|
94
98
|
---
|
|
95
99
|
|
|
96
100
|
## 🧩 Highlights (v2.0+)
|
|
97
101
|
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
102
|
+
- Unified Configuration: Use `@configured` to bind both flat (ENV-like) and tree (YAML/JSON) sources via the `configuration(...)` builder (ADR-0010).
|
|
103
|
+
- Async-aware AOP system: Method interceptors via `@intercepted_by`.
|
|
104
|
+
- Scoped resolution: singleton, prototype, request, session, transaction, and custom scopes.
|
|
105
|
+
- `UnifiedComponentProxy`: Transparent `lazy=True` and AOP proxy supporting serialization.
|
|
106
|
+
- Tree-based configuration runtime: Advanced mapping with reusable adapters and discriminators (`Annotated[Union[...], Discriminator(...)]`).
|
|
107
|
+
- Observable container context: Built-in stats, health checks (`@health`), observer hooks (`ContainerObserver`), dependency graph export (`export_graph`), and async cleanup.
|
|
104
108
|
|
|
105
109
|
---
|
|
106
110
|
|
|
@@ -108,26 +112,15 @@ As Python systems evolve, wiring dependencies by hand becomes fragile and unmain
|
|
|
108
112
|
|
|
109
113
|
```bash
|
|
110
114
|
pip install pico-ioc
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
For optional features, you can install extras:
|
|
114
|
-
|
|
115
|
-
* **YAML Configuration:**
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
pip install pico-ioc[yaml]
|
|
119
|
-
```
|
|
115
|
+
```
|
|
120
116
|
|
|
121
|
-
|
|
117
|
+
Optional extras:
|
|
122
118
|
|
|
123
|
-
|
|
119
|
+
- YAML configuration support (requires PyYAML)
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# as export_graph generates the .dot file content directly.
|
|
129
|
-
# pip install pico-ioc[graphviz] # Consider removing if not used by code
|
|
130
|
-
```
|
|
121
|
+
```bash
|
|
122
|
+
pip install pico-ioc[yaml]
|
|
123
|
+
```
|
|
131
124
|
|
|
132
125
|
-----
|
|
133
126
|
|
|
@@ -139,7 +132,7 @@ from dataclasses import dataclass
|
|
|
139
132
|
from pico_ioc import component, configured, configuration, init, EnvSource
|
|
140
133
|
|
|
141
134
|
# 1. Define configuration with @configured
|
|
142
|
-
@configured(prefix="APP_", mapping="auto")
|
|
135
|
+
@configured(prefix="APP_", mapping="auto") # Auto-detects flat mapping
|
|
143
136
|
@dataclass
|
|
144
137
|
class Config:
|
|
145
138
|
db_url: str = "sqlite:///demo.db"
|
|
@@ -147,14 +140,14 @@ class Config:
|
|
|
147
140
|
# 2. Define components
|
|
148
141
|
@component
|
|
149
142
|
class Repo:
|
|
150
|
-
def __init__(self, cfg: Config):
|
|
143
|
+
def __init__(self, cfg: Config): # Inject config
|
|
151
144
|
self.cfg = cfg
|
|
152
145
|
def fetch(self):
|
|
153
146
|
return f"fetching from {self.cfg.db_url}"
|
|
154
147
|
|
|
155
148
|
@component
|
|
156
149
|
class Service:
|
|
157
|
-
def __init__(self, repo: Repo):
|
|
150
|
+
def __init__(self, repo: Repo): # Inject Repo
|
|
158
151
|
self.repo = repo
|
|
159
152
|
def run(self):
|
|
160
153
|
return self.repo.fetch()
|
|
@@ -164,11 +157,11 @@ os.environ['APP_DB_URL'] = 'postgresql://user:pass@host/db'
|
|
|
164
157
|
|
|
165
158
|
# 3. Build configuration context
|
|
166
159
|
config_ctx = configuration(
|
|
167
|
-
EnvSource(prefix="")
|
|
160
|
+
EnvSource(prefix="") # Read APP_DB_URL from environment
|
|
168
161
|
)
|
|
169
162
|
|
|
170
163
|
# 4. Initialize container
|
|
171
|
-
container = init(modules=[__name__], config=config_ctx)
|
|
164
|
+
container = init(modules=[__name__], config=config_ctx) # Pass context via 'config'
|
|
172
165
|
|
|
173
166
|
# 5. Get and use the service
|
|
174
167
|
svc = container.get(Service)
|
|
@@ -178,7 +171,7 @@ print(svc.run())
|
|
|
178
171
|
del os.environ['APP_DB_URL']
|
|
179
172
|
```
|
|
180
173
|
|
|
181
|
-
|
|
174
|
+
Output:
|
|
182
175
|
|
|
183
176
|
```
|
|
184
177
|
fetching from postgresql://user:pass@host/db
|
|
@@ -199,7 +192,7 @@ test_config_ctx = configuration()
|
|
|
199
192
|
container = init(
|
|
200
193
|
modules=[__name__],
|
|
201
194
|
config=test_config_ctx,
|
|
202
|
-
overrides={Repo: FakeRepo()}
|
|
195
|
+
overrides={Repo: FakeRepo()} # Replace Repo with FakeRepo
|
|
203
196
|
)
|
|
204
197
|
|
|
205
198
|
svc = container.get(Service)
|
|
@@ -208,10 +201,56 @@ assert svc.run() == "fake-data"
|
|
|
208
201
|
|
|
209
202
|
-----
|
|
210
203
|
|
|
204
|
+
## 🧰 Profiles
|
|
205
|
+
|
|
206
|
+
Use profiles to enable/disable components or configuration branches conditionally.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Enable "test" profile when bootstrapping the container
|
|
210
|
+
container = init(
|
|
211
|
+
modules=[__name__],
|
|
212
|
+
profiles=["test"]
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Profiles are typically referenced in decorators or configuration mappings to include/exclude components and bindings.
|
|
217
|
+
|
|
218
|
+
-----
|
|
219
|
+
|
|
220
|
+
## ⚡ Async Components
|
|
221
|
+
|
|
222
|
+
Pico-IoC supports async lifecycle and resolution.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import asyncio
|
|
226
|
+
from pico_ioc import component, init
|
|
227
|
+
|
|
228
|
+
@component
|
|
229
|
+
class AsyncRepo:
|
|
230
|
+
async def __ainit__(self):
|
|
231
|
+
# e.g., open async connections
|
|
232
|
+
self.ready = True
|
|
233
|
+
|
|
234
|
+
async def fetch(self):
|
|
235
|
+
return "async-data"
|
|
236
|
+
|
|
237
|
+
async def main():
|
|
238
|
+
container = init(modules=[__name__])
|
|
239
|
+
repo = await container.aget(AsyncRepo) # Async resolution
|
|
240
|
+
print(await repo.fetch())
|
|
241
|
+
|
|
242
|
+
asyncio.run(main())
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- `__ainit__` runs after construction if defined.
|
|
246
|
+
- Use `container.aget(Type)` to resolve components that require async initialization or whose providers are async.
|
|
247
|
+
|
|
248
|
+
-----
|
|
249
|
+
|
|
211
250
|
## 🩺 Lifecycle & AOP
|
|
212
251
|
|
|
213
252
|
```python
|
|
214
|
-
import time
|
|
253
|
+
import time
|
|
215
254
|
from pico_ioc import component, init, intercepted_by, MethodInterceptor, MethodCtx
|
|
216
255
|
|
|
217
256
|
# Define an interceptor component
|
|
@@ -232,7 +271,7 @@ class LogInterceptor(MethodInterceptor):
|
|
|
232
271
|
|
|
233
272
|
@component
|
|
234
273
|
class Demo:
|
|
235
|
-
@intercepted_by(LogInterceptor)
|
|
274
|
+
@intercepted_by(LogInterceptor) # Apply the interceptor
|
|
236
275
|
def work(self):
|
|
237
276
|
print(" Working...")
|
|
238
277
|
time.sleep(0.01)
|
|
@@ -244,7 +283,7 @@ result = c.get(Demo).work()
|
|
|
244
283
|
print(f"Result: {result}")
|
|
245
284
|
```
|
|
246
285
|
|
|
247
|
-
|
|
286
|
+
Output:
|
|
248
287
|
|
|
249
288
|
```
|
|
250
289
|
→ calling Demo.work
|
|
@@ -255,19 +294,41 @@ Result: ok
|
|
|
255
294
|
|
|
256
295
|
-----
|
|
257
296
|
|
|
297
|
+
## 👁️ Observability & Cleanup
|
|
298
|
+
|
|
299
|
+
- Export a dependency graph in DOT format:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
c = init(modules=[...])
|
|
303
|
+
dot = c.export_graph() # Returns DOT graph as a string
|
|
304
|
+
with open("dependencies.dot", "w") as f:
|
|
305
|
+
f.write(dot)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- Health checks:
|
|
309
|
+
- Annotate health probes inside components with `@health` for container-level reporting.
|
|
310
|
+
- The container exposes health information that can be queried in observability tooling.
|
|
311
|
+
|
|
312
|
+
- Container cleanup:
|
|
313
|
+
- For sync components: `container.close()`
|
|
314
|
+
- For async components/resources: `await container.aclose()`
|
|
315
|
+
|
|
316
|
+
Use cleanup in application shutdown hooks to release resources deterministically.
|
|
317
|
+
|
|
318
|
+
-----
|
|
319
|
+
|
|
258
320
|
## 📖 Documentation
|
|
259
321
|
|
|
260
322
|
The full documentation is available within the `docs/` directory of the project repository. Start with `docs/README.md` for navigation.
|
|
261
323
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* **ADR Index:** `docs/adr/README.md`
|
|
324
|
+
- Getting Started: `docs/getting-started.md`
|
|
325
|
+
- User Guide: `docs/user-guide/README.md`
|
|
326
|
+
- Advanced Features: `docs/advanced-features/README.md`
|
|
327
|
+
- Observability: `docs/observability/README.md`
|
|
328
|
+
- Cookbook (Patterns): `docs/cookbook/README.md`
|
|
329
|
+
- Architecture: `docs/architecture/README.md`
|
|
330
|
+
- API Reference: `docs/api-reference/README.md`
|
|
331
|
+
- ADR Index: `docs/adr/README.md`
|
|
271
332
|
|
|
272
333
|
-----
|
|
273
334
|
|
|
@@ -282,11 +343,10 @@ tox
|
|
|
282
343
|
|
|
283
344
|
## 🧾 Changelog
|
|
284
345
|
|
|
285
|
-
See [CHANGELOG.md](./CHANGELOG.md) —
|
|
346
|
+
See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.0+.
|
|
286
347
|
|
|
287
348
|
-----
|
|
288
349
|
|
|
289
350
|
## 📜 License
|
|
290
351
|
|
|
291
352
|
MIT — [LICENSE](https://opensource.org/licenses/MIT)
|
|
292
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pico_ioc/__init__.py,sha256=i25Obx7aH_Oy5b6yjjnCswDgni7InIjrGEcG6vLAw6I,2414
|
|
2
|
-
pico_ioc/_version.py,sha256=
|
|
3
|
-
pico_ioc/analysis.py,sha256=
|
|
2
|
+
pico_ioc/_version.py,sha256=m5qImnzcnIhayvILFVqEnXPYsN-vE0vxokygykKhRfw,22
|
|
3
|
+
pico_ioc/analysis.py,sha256=Iy3fuXCVLV8xtT-qp-uxsb1QptHBLLrLYbTSfDkQ-OA,4145
|
|
4
4
|
pico_ioc/aop.py,sha256=XcyzsuKPrVPk1_Jad7Mn-qwoL1y0ZuVWwRZBA-CslJk,13301
|
|
5
5
|
pico_ioc/api.py,sha256=0pcRFHzhDcX8ijd67xAsVrTejwXuJKz7kTKRUrIuX2s,6161
|
|
6
6
|
pico_ioc/component_scanner.py,sha256=S-9XNxrgyq_JFdc4Uqn2bEb-HxafSgIWylIurxyN_UA,7955
|
|
@@ -8,18 +8,18 @@ pico_ioc/config_builder.py,sha256=7kcYIq1Yrb46Tic7uLeaCDvLA-Sa_p1PIoGF00mivso,28
|
|
|
8
8
|
pico_ioc/config_registrar.py,sha256=34iNQY1TUEPTXbb-QV1T-c5VKAn18hBcNt5MLhzDSfY,8456
|
|
9
9
|
pico_ioc/config_runtime.py,sha256=hiL1kCxhpjbfOdUaH71jMGNESDpWsaJkQXh7q1T71bg,12781
|
|
10
10
|
pico_ioc/constants.py,sha256=AhIt0ieDZ9Turxb_YbNzj11wUbBbzjKfWh1BDlSx2Nw,183
|
|
11
|
-
pico_ioc/container.py,sha256=
|
|
11
|
+
pico_ioc/container.py,sha256=Ys1yLjiB3Qxxm_fvWCEYLSeaJ18LseWmXueAW8kHunk,20874
|
|
12
12
|
pico_ioc/decorators.py,sha256=ru_YeqyJ3gbfb6M8WeJZlBxfcBBEuGDvxpHJGzU6FIs,6412
|
|
13
13
|
pico_ioc/dependency_validator.py,sha256=BIR6pKntACiabF6CjNZ3m00RMnet9BPK1_9y1iCJ5KQ,4144
|
|
14
14
|
pico_ioc/event_bus.py,sha256=nOL91JLYxap9kbb-HBGEhOVwtXN_bfI4q0mtSRZFlHk,8434
|
|
15
15
|
pico_ioc/exceptions.py,sha256=FBuajj5g29hAGODt2tAWuy2sG5mQojdSddaqFzim-aY,2383
|
|
16
16
|
pico_ioc/factory.py,sha256=oJXx_BYJuvV8oxYzs5I3gx9WM6uLYZ8GCc43gukNanc,1671
|
|
17
|
-
pico_ioc/locator.py,sha256=
|
|
17
|
+
pico_ioc/locator.py,sha256=JD6psgdGGsBoCwov-G76BrmTfKUoJ22sdwa6wVdmQV8,5064
|
|
18
18
|
pico_ioc/provider_selector.py,sha256=pU7NbI5vifvUlJEjlRJmvveQUZVD47T24QmiP0CHRw0,1213
|
|
19
19
|
pico_ioc/registrar.py,sha256=hIk48nXghTdA3WBljCbw2q8J_6F_hCk1ljSi4Pb8P3A,8368
|
|
20
20
|
pico_ioc/scope.py,sha256=hOdTmjjfrRt8APXoS3lbTbSPxILi7flBXz_qpIkpoKw,6137
|
|
21
|
-
pico_ioc-2.1.
|
|
22
|
-
pico_ioc-2.1.
|
|
23
|
-
pico_ioc-2.1.
|
|
24
|
-
pico_ioc-2.1.
|
|
25
|
-
pico_ioc-2.1.
|
|
21
|
+
pico_ioc-2.1.2.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
|
|
22
|
+
pico_ioc-2.1.2.dist-info/METADATA,sha256=yerxK_c9JcZxnKqB-nWQL6bSovNLse9Qa67o2jD9R3I,12339
|
|
23
|
+
pico_ioc-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
pico_ioc-2.1.2.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
|
|
25
|
+
pico_ioc-2.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|