pico-ioc 2.1.3__py3-none-any.whl → 2.2.1__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/__init__.py +2 -1
- pico_ioc/_version.py +1 -1
- pico_ioc/aop.py +13 -11
- pico_ioc/api.py +9 -3
- pico_ioc/component_scanner.py +48 -24
- pico_ioc/container.py +5 -1
- pico_ioc/registrar.py +3 -5
- pico_ioc/scope.py +15 -8
- {pico_ioc-2.1.3.dist-info → pico_ioc-2.2.1.dist-info}/METADATA +47 -51
- {pico_ioc-2.1.3.dist-info → pico_ioc-2.2.1.dist-info}/RECORD +13 -13
- {pico_ioc-2.1.3.dist-info → pico_ioc-2.2.1.dist-info}/WHEEL +1 -1
- {pico_ioc-2.1.3.dist-info → pico_ioc-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.1.3.dist-info → pico_ioc-2.2.1.dist-info}/top_level.txt +0 -0
pico_ioc/__init__.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# src/pico_ioc/__init__.py
|
|
2
1
|
from .constants import LOGGER_NAME, LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
3
2
|
from .exceptions import (
|
|
4
3
|
PicoError,
|
|
@@ -31,6 +30,7 @@ from .container import PicoContainer
|
|
|
31
30
|
from .event_bus import EventBus, ExecPolicy, ErrorPolicy, Event, subscribe, AutoSubscriberMixin
|
|
32
31
|
from .config_runtime import JsonTreeSource, YamlTreeSource, DictSource, Discriminator, Value
|
|
33
32
|
from .analysis import DependencyRequest, analyze_callable_dependencies
|
|
33
|
+
from .component_scanner import CustomScanner
|
|
34
34
|
|
|
35
35
|
__all__ = [
|
|
36
36
|
"LOGGER_NAME",
|
|
@@ -91,4 +91,5 @@ __all__ = [
|
|
|
91
91
|
"Value",
|
|
92
92
|
"DependencyRequest",
|
|
93
93
|
"analyze_callable_dependencies",
|
|
94
|
+
"CustomScanner",
|
|
94
95
|
]
|
pico_ioc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.1
|
|
1
|
+
__version__ = '2.2.1'
|
pico_ioc/aop.py
CHANGED
|
@@ -145,23 +145,25 @@ class UnifiedComponentProxy:
|
|
|
145
145
|
return
|
|
146
146
|
|
|
147
147
|
lock = object.__getattribute__(self, "_lock")
|
|
148
|
-
tgt = object.__getattribute__(self, "_target")
|
|
149
|
-
if tgt is not None:
|
|
150
|
-
return
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
with lock:
|
|
150
|
+
# Double-check inside lock to prevent race condition
|
|
151
|
+
tgt = object.__getattribute__(self, "_target")
|
|
152
|
+
if tgt is not None:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
creator = object.__getattribute__(self, "_creator")
|
|
156
|
+
container = object.__getattribute__(self, "_container")
|
|
157
|
+
|
|
158
|
+
tgt = creator()
|
|
159
|
+
object.__setattr__(self, "_target", tgt)
|
|
160
|
+
|
|
161
|
+
# Run configure methods outside the lock to avoid blocking
|
|
157
162
|
if container and hasattr(container, "_run_configure_methods"):
|
|
158
163
|
res = container._run_configure_methods(tgt)
|
|
159
164
|
if inspect.isawaitable(res):
|
|
160
165
|
await res
|
|
161
166
|
|
|
162
|
-
with lock:
|
|
163
|
-
object.__setattr__(self, "_target", tgt)
|
|
164
|
-
|
|
165
167
|
def _scope_signature(self) -> Tuple[Any, ...]:
|
|
166
168
|
container = object.__getattribute__(self, "_container")
|
|
167
169
|
key = object.__getattribute__(self, "_component_key")
|
pico_ioc/api.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# src/pico_ioc/api.py
|
|
2
|
-
|
|
3
1
|
import importlib
|
|
4
2
|
import pkgutil
|
|
5
3
|
import logging
|
|
@@ -13,6 +11,8 @@ from .container import PicoContainer
|
|
|
13
11
|
from .decorators import component, factory, provides, Qualifier, configure, cleanup, configured
|
|
14
12
|
from .config_builder import ContextConfig, configuration
|
|
15
13
|
from .registrar import Registrar
|
|
14
|
+
from .aop import ContainerObserver
|
|
15
|
+
from .component_scanner import CustomScanner
|
|
16
16
|
|
|
17
17
|
KeyT = Union[str, type]
|
|
18
18
|
Provider = Callable[[], Any]
|
|
@@ -63,7 +63,8 @@ def init(
|
|
|
63
63
|
custom_scopes: Optional[Iterable[str]] = None,
|
|
64
64
|
validate_only: bool = False,
|
|
65
65
|
container_id: Optional[str] = None,
|
|
66
|
-
observers: Optional[List[
|
|
66
|
+
observers: Optional[List[ContainerObserver]] = None,
|
|
67
|
+
custom_scanners: Optional[List[CustomScanner]] = None,
|
|
67
68
|
) -> PicoContainer:
|
|
68
69
|
active = tuple(p.strip() for p in profiles if p)
|
|
69
70
|
|
|
@@ -82,6 +83,11 @@ def init(
|
|
|
82
83
|
|
|
83
84
|
pico = PicoContainer(factory, caches, scopes, container_id=container_id, profiles=active, observers=observers or [])
|
|
84
85
|
registrar = Registrar(factory, profiles=active, environ=environ, logger=logger, config=config)
|
|
86
|
+
|
|
87
|
+
if custom_scanners:
|
|
88
|
+
for scanner in custom_scanners:
|
|
89
|
+
registrar.register_custom_scanner(scanner)
|
|
90
|
+
|
|
85
91
|
for m in _iter_input_modules(modules):
|
|
86
92
|
registrar.register_module(m)
|
|
87
93
|
|
pico_ioc/component_scanner.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import os
|
|
3
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Set
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Set, Protocol
|
|
4
4
|
from .constants import PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
5
5
|
from .factory import ProviderMetadata, DeferredProvider
|
|
6
6
|
from .decorators import get_return_type
|
|
@@ -10,6 +10,10 @@ from .analysis import analyze_callable_dependencies, DependencyRequest
|
|
|
10
10
|
KeyT = Union[str, type]
|
|
11
11
|
Provider = Callable[[], Any]
|
|
12
12
|
|
|
13
|
+
class CustomScanner(Protocol):
|
|
14
|
+
def should_scan(self, obj: Any) -> bool: ...
|
|
15
|
+
def scan(self, obj: Any) -> Optional[Tuple[KeyT, Provider, ProviderMetadata]]: ...
|
|
16
|
+
|
|
13
17
|
class ComponentScanner:
|
|
14
18
|
def __init__(self, profiles: Set[str], environ: Dict[str, str], config_manager: ConfigurationManager):
|
|
15
19
|
self._profiles = profiles
|
|
@@ -19,6 +23,10 @@ class ComponentScanner:
|
|
|
19
23
|
self._on_missing: List[Tuple[int, KeyT, type]] = []
|
|
20
24
|
self._deferred: List[DeferredProvider] = []
|
|
21
25
|
self._provides_functions: Dict[KeyT, Callable[..., Any]] = {}
|
|
26
|
+
self._custom_scanners: List[CustomScanner] = []
|
|
27
|
+
|
|
28
|
+
def register_custom_scanner(self, scanner: CustomScanner) -> None:
|
|
29
|
+
self._custom_scanners.append(scanner)
|
|
22
30
|
|
|
23
31
|
def get_scan_results(self) -> Tuple[Dict[KeyT, List[Tuple[bool, Provider, ProviderMetadata]]], List[Tuple[int, KeyT, type]], List[DeferredProvider], Dict[KeyT, Callable[..., Any]]]:
|
|
24
32
|
return self._candidates, self._on_missing, self._deferred, self._provides_functions
|
|
@@ -86,7 +94,6 @@ class ComponentScanner:
|
|
|
86
94
|
if has_instance_provides:
|
|
87
95
|
factory_deps = analyze_callable_dependencies(cls.__init__)
|
|
88
96
|
|
|
89
|
-
|
|
90
97
|
for name in dir(cls):
|
|
91
98
|
try:
|
|
92
99
|
raw = inspect.getattr_static(cls, name)
|
|
@@ -140,27 +147,44 @@ class ComponentScanner:
|
|
|
140
147
|
self._queue(k, provider, md)
|
|
141
148
|
self._provides_functions[k] = fn
|
|
142
149
|
|
|
150
|
+
def _try_custom_scanners(self, obj: Any) -> bool:
|
|
151
|
+
for scanner in self._custom_scanners:
|
|
152
|
+
if scanner.should_scan(obj):
|
|
153
|
+
result = scanner.scan(obj)
|
|
154
|
+
if result:
|
|
155
|
+
key, provider, md = result
|
|
156
|
+
self._queue(key, provider, md)
|
|
157
|
+
if isinstance(provider, DeferredProvider):
|
|
158
|
+
self._deferred.append(provider)
|
|
159
|
+
return True
|
|
160
|
+
return False
|
|
161
|
+
|
|
143
162
|
def scan_module(self, module: Any) -> None:
|
|
144
163
|
for _, obj in inspect.getmembers(module):
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
self.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
if self._try_custom_scanners(obj):
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if inspect.isclass(obj) or getattr(obj, "_is_protocol", False):
|
|
168
|
+
if inspect.isclass(obj):
|
|
169
|
+
meta = getattr(obj, PICO_META, {})
|
|
170
|
+
|
|
171
|
+
if "on_missing" in meta:
|
|
172
|
+
sel = meta["on_missing"]["selector"]
|
|
173
|
+
pr = int(meta["on_missing"].get("priority", 0))
|
|
174
|
+
self._on_missing.append((pr, sel, obj))
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
infra = getattr(obj, PICO_INFRA, None)
|
|
178
|
+
if infra == "component":
|
|
179
|
+
self._register_component_class(obj)
|
|
180
|
+
elif infra == "factory":
|
|
181
|
+
self._register_factory_class(obj)
|
|
182
|
+
elif infra == "configured":
|
|
183
|
+
enabled = self._enabled_by_condition(obj)
|
|
184
|
+
reg_data = self._config_manager.register_configured_class(obj, enabled)
|
|
185
|
+
if reg_data:
|
|
186
|
+
self._queue(reg_data[0], reg_data[1], reg_data[2])
|
|
187
|
+
|
|
188
|
+
elif inspect.isfunction(obj):
|
|
189
|
+
if getattr(obj, PICO_INFRA, None) == "provides":
|
|
190
|
+
self._register_provides_function(obj)
|
pico_ioc/container.py
CHANGED
|
@@ -384,6 +384,10 @@ class PicoContainer:
|
|
|
384
384
|
self.cleanup_all()
|
|
385
385
|
PicoContainer._container_registry.pop(self.container_id, None)
|
|
386
386
|
|
|
387
|
+
async def ashutdown(self) -> None:
|
|
388
|
+
await self.cleanup_all_async()
|
|
389
|
+
PicoContainer._container_registry.pop(self.container_id, None)
|
|
390
|
+
|
|
387
391
|
def build_resolution_graph(self):
|
|
388
392
|
return _build_resolution_graph(self._locator)
|
|
389
393
|
|
|
@@ -434,7 +438,7 @@ class PicoContainer:
|
|
|
434
438
|
pid = _node_id(parent)
|
|
435
439
|
for child in deps:
|
|
436
440
|
cid = _node_id(child)
|
|
437
|
-
lines.append(f" {pid} -> {
|
|
441
|
+
lines.append(f" {pid} -> {cid};")
|
|
438
442
|
|
|
439
443
|
lines.append("}")
|
|
440
444
|
|
pico_ioc/registrar.py
CHANGED
|
@@ -15,7 +15,7 @@ from .config_runtime import TreeSource
|
|
|
15
15
|
from .config_registrar import ConfigurationManager
|
|
16
16
|
from .provider_selector import ProviderSelector
|
|
17
17
|
from .dependency_validator import DependencyValidator
|
|
18
|
-
from .component_scanner import ComponentScanner
|
|
18
|
+
from .component_scanner import ComponentScanner, CustomScanner
|
|
19
19
|
from .analysis import analyze_callable_dependencies, DependencyRequest
|
|
20
20
|
from .container import PicoContainer
|
|
21
21
|
|
|
@@ -49,6 +49,8 @@ class Registrar:
|
|
|
49
49
|
self._deferred: List[DeferredProvider] = []
|
|
50
50
|
self._provides_functions: Dict[KeyT, Callable[..., Any]] = {}
|
|
51
51
|
|
|
52
|
+
def register_custom_scanner(self, scanner: CustomScanner) -> None:
|
|
53
|
+
self._scanner.register_custom_scanner(scanner)
|
|
52
54
|
|
|
53
55
|
def locator(self) -> ComponentLocator:
|
|
54
56
|
loc = ComponentLocator(dict(self._metadata), dict(self._indexes))
|
|
@@ -65,16 +67,13 @@ class Registrar:
|
|
|
65
67
|
return UnifiedComponentProxy(container=_p, object_creator=_orig, component_key=_k)
|
|
66
68
|
self._factory.bind(key, lazy_proxy_provider)
|
|
67
69
|
|
|
68
|
-
|
|
69
70
|
def _bind_if_absent(self, key: KeyT, provider: Provider) -> None:
|
|
70
71
|
if not self._factory.has(key):
|
|
71
72
|
self._factory.bind(key, provider)
|
|
72
73
|
|
|
73
|
-
|
|
74
74
|
def register_module(self, module: Any) -> None:
|
|
75
75
|
self._scanner.scan_module(module)
|
|
76
76
|
|
|
77
|
-
|
|
78
77
|
def _find_md_for_type(self, t: type) -> Optional[ProviderMetadata]:
|
|
79
78
|
cands: List[ProviderMetadata] = []
|
|
80
79
|
for md in self._metadata.values():
|
|
@@ -133,7 +132,6 @@ class Registrar:
|
|
|
133
132
|
if md.pico_name is not None:
|
|
134
133
|
add("pico_name", md.pico_name, k)
|
|
135
134
|
|
|
136
|
-
|
|
137
135
|
def finalize(self, overrides: Optional[Dict[KeyT, Any]], *, pico_instance: PicoContainer) -> None:
|
|
138
136
|
candidates, on_missing, deferred_providers, provides_functions = self._scanner.get_scan_results()
|
|
139
137
|
self._deferred = deferred_providers
|
pico_ioc/scope.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import contextvars
|
|
2
2
|
import inspect
|
|
3
|
+
import logging
|
|
3
4
|
from typing import Any, Dict, Optional, Tuple
|
|
4
5
|
from collections import OrderedDict
|
|
5
6
|
from .exceptions import ScopeError
|
|
6
7
|
|
|
8
|
+
_logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
7
10
|
class ScopeProtocol:
|
|
8
11
|
def get_id(self) -> Any | None: ...
|
|
9
12
|
|
|
@@ -92,10 +95,9 @@ class ScopeManager:
|
|
|
92
95
|
return self.signature(self.names())
|
|
93
96
|
|
|
94
97
|
class ScopedCaches:
|
|
95
|
-
def __init__(self
|
|
98
|
+
def __init__(self) -> None:
|
|
96
99
|
self._singleton = ComponentContainer()
|
|
97
100
|
self._by_scope: Dict[str, Dict[Any, ComponentContainer]] = {}
|
|
98
|
-
self._max = int(max_scopes_per_type)
|
|
99
101
|
self._no_cache = _NoCacheContainer()
|
|
100
102
|
|
|
101
103
|
def _cleanup_object(self, obj: Any) -> None:
|
|
@@ -109,10 +111,15 @@ class ScopedCaches:
|
|
|
109
111
|
if meta.get("cleanup", False):
|
|
110
112
|
try:
|
|
111
113
|
m()
|
|
112
|
-
except Exception:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
except Exception as e:
|
|
115
|
+
_logger.warning(
|
|
116
|
+
"Cleanup method %s.%s failed: %s",
|
|
117
|
+
type(obj).__name__,
|
|
118
|
+
getattr(m, "__name__", "<unknown>"),
|
|
119
|
+
e
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
_logger.debug("Failed to inspect object for cleanup: %s", e)
|
|
116
123
|
|
|
117
124
|
def cleanup_scope(self, scope_name: str, scope_id: Any) -> None:
|
|
118
125
|
bucket = self._by_scope.get(scope_name)
|
|
@@ -124,8 +131,8 @@ class ScopedCaches:
|
|
|
124
131
|
try:
|
|
125
132
|
for _, obj in container.items():
|
|
126
133
|
self._cleanup_object(obj)
|
|
127
|
-
except Exception:
|
|
128
|
-
|
|
134
|
+
except Exception as e:
|
|
135
|
+
_logger.debug("Failed to cleanup container: %s", e)
|
|
129
136
|
|
|
130
137
|
def for_scope(self, scopes: ScopeManager, scope: str) -> ComponentContainer:
|
|
131
138
|
if scope == "singleton":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pico-ioc
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
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
|
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
|
28
28
|
Project-URL: Homepage, https://github.com/dperezcabrera/pico-ioc
|
|
29
29
|
Project-URL: Repository, https://github.com/dperezcabrera/pico-ioc
|
|
30
30
|
Project-URL: Issue Tracker, https://github.com/dperezcabrera/pico-ioc/issues
|
|
31
|
-
Keywords: ioc,di,dependency
|
|
31
|
+
Keywords: python,ioc,dependency-injection,di-container,inversion-of-control,ioc-container,zero-dependency,minimalistic,async,asyncio,modular,pluggable,ioc-framework,ioc-containers,inversion-of-control-container
|
|
32
32
|
Classifier: Development Status :: 4 - Beta
|
|
33
33
|
Classifier: Programming Language :: Python :: 3
|
|
34
34
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -62,7 +62,6 @@ Dynamic: license-file
|
|
|
62
62
|
[](https://dperezcabrera.github.io/pico-ioc/)
|
|
63
63
|
[](https://dperezcabrera.github.io/learn-pico-ioc/)
|
|
64
64
|
|
|
65
|
-
|
|
66
65
|
**Pico-IoC** is a **lightweight, async-ready, decorator-driven IoC container** built for clarity, testability, and performance.
|
|
67
66
|
It brings Inversion of Control and dependency injection to Python in a deterministic, modern, and framework-agnostic way.
|
|
68
67
|
|
|
@@ -97,14 +96,14 @@ Pico-IoC eliminates that friction by letting you declare how components relate
|
|
|
97
96
|
|
|
98
97
|
---
|
|
99
98
|
|
|
100
|
-
## 🧩 Highlights (v2.
|
|
99
|
+
## 🧩 Highlights (v2.2+)
|
|
101
100
|
|
|
102
|
-
- Unified Configuration
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
- Tree-based configuration
|
|
107
|
-
- Observable
|
|
101
|
+
- **Unified Configuration**: Use `@configured` to bind both flat (ENV-like) and tree (YAML/JSON) sources via the `configuration(...)` builder (ADR-0010).
|
|
102
|
+
- **Extensible Scanning**: Use `CustomScanner` to hook into the discovery phase and register functions or custom decorators (ADR-0011).
|
|
103
|
+
- **Async-aware AOP**: Method interceptors via `@intercepted_by`.
|
|
104
|
+
- **Scoped resolution**: singleton, prototype, request, session, transaction, and custom scopes.
|
|
105
|
+
- **Tree-based configuration**: Advanced mapping with reusable adapters (`Annotated[Union[...], Discriminator(...)]`).
|
|
106
|
+
- **Observable context**: Built-in stats, health checks (`@health`), observer hooks (`ContainerObserver`), and dependency graph export.
|
|
108
107
|
|
|
109
108
|
---
|
|
110
109
|
|
|
@@ -116,20 +115,21 @@ pip install pico-ioc
|
|
|
116
115
|
|
|
117
116
|
Optional extras:
|
|
118
117
|
|
|
119
|
-
- YAML configuration support (requires PyYAML)
|
|
118
|
+
- YAML configuration support (requires PyYAML)
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
```bash
|
|
121
|
+
pip install pico-ioc[yaml]
|
|
122
|
+
```
|
|
124
123
|
|
|
125
124
|
-----
|
|
126
125
|
|
|
127
|
-
### ⚠️ Important Note
|
|
126
|
+
### ⚠️ Important Note
|
|
127
|
+
|
|
128
|
+
**Breaking Behavior in Scope Management (v2.1.3+):**
|
|
129
|
+
**Scope LRU Eviction has been removed** to guarantee data integrity.
|
|
128
130
|
|
|
129
|
-
**
|
|
130
|
-
|
|
131
|
-
* **If you use `pico-fastapi`:** You are safe (the middleware handles cleanup automatically).
|
|
132
|
-
* **If you perform manual scope management:** You **must** explicitly call `container._caches.cleanup_scope("scope_name", scope_id)` when a context ends. Failing to do so will result in a memory leak, as scopes are no longer automatically discarded when the container fills up.
|
|
131
|
+
* **Frameworks (pico-fastapi):** Handled automatically.
|
|
132
|
+
* **Manual usage:** You **must** explicitly call `container._caches.cleanup_scope("scope_name", scope_id)` when a context ends to prevent memory leaks.
|
|
133
133
|
|
|
134
134
|
-----
|
|
135
135
|
|
|
@@ -247,12 +247,16 @@ async def main():
|
|
|
247
247
|
container = init(modules=[__name__])
|
|
248
248
|
repo = await container.aget(AsyncRepo) # Async resolution
|
|
249
249
|
print(await repo.fetch())
|
|
250
|
+
|
|
251
|
+
# Graceful async shutdown (calls @cleanup async methods)
|
|
252
|
+
await container.ashutdown()
|
|
250
253
|
|
|
251
254
|
asyncio.run(main())
|
|
252
255
|
```
|
|
253
256
|
|
|
254
|
-
- `__ainit__` runs after construction if defined.
|
|
255
|
-
- Use `container.aget(Type)` to resolve components that require async initialization
|
|
257
|
+
- `__ainit__` runs after construction if defined.
|
|
258
|
+
- Use `container.aget(Type)` to resolve components that require async initialization.
|
|
259
|
+
- Use `await container.ashutdown()` to close resources cleanly.
|
|
256
260
|
|
|
257
261
|
-----
|
|
258
262
|
|
|
@@ -292,35 +296,26 @@ result = c.get(Demo).work()
|
|
|
292
296
|
print(f"Result: {result}")
|
|
293
297
|
```
|
|
294
298
|
|
|
295
|
-
Output:
|
|
296
|
-
|
|
297
|
-
```
|
|
298
|
-
→ calling Demo.work
|
|
299
|
-
Working...
|
|
300
|
-
← Demo.work done (10.xxms)
|
|
301
|
-
Result: ok
|
|
302
|
-
```
|
|
303
|
-
|
|
304
299
|
-----
|
|
305
300
|
|
|
306
301
|
## 👁️ Observability & Cleanup
|
|
307
302
|
|
|
308
|
-
- Export a dependency graph in DOT format:
|
|
303
|
+
- Export a dependency graph in DOT format:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
c = init(modules=[...])
|
|
307
|
+
c.export_graph("dependencies.dot") # Writes directly to file
|
|
308
|
+
```
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
c = init(modules=[...])
|
|
312
|
-
dot = c.export_graph() # Returns DOT graph as a string
|
|
313
|
-
with open("dependencies.dot", "w") as f:
|
|
314
|
-
f.write(dot)
|
|
315
|
-
```
|
|
310
|
+
- Health checks:
|
|
316
311
|
|
|
317
|
-
-
|
|
318
|
-
|
|
319
|
-
- The container exposes health information that can be queried in observability tooling.
|
|
312
|
+
- Annotate health probes inside components with `@health` for container-level reporting.
|
|
313
|
+
- The container exposes health information that can be queried in observability tooling.
|
|
320
314
|
|
|
321
|
-
- Container cleanup:
|
|
322
|
-
|
|
323
|
-
|
|
315
|
+
- Container cleanup:
|
|
316
|
+
|
|
317
|
+
- For sync apps: `container.shutdown()`
|
|
318
|
+
- For async apps: `await container.ashutdown()`
|
|
324
319
|
|
|
325
320
|
Use cleanup in application shutdown hooks to release resources deterministically.
|
|
326
321
|
|
|
@@ -330,14 +325,14 @@ Use cleanup in application shutdown hooks to release resources deterministically
|
|
|
330
325
|
|
|
331
326
|
The full documentation is available within the `docs/` directory of the project repository. Start with `docs/README.md` for navigation.
|
|
332
327
|
|
|
333
|
-
- Getting Started: `docs/getting-started.md`
|
|
334
|
-
- User Guide: `docs/user-guide/README.md`
|
|
335
|
-
- Advanced Features: `docs/advanced-features/README.md`
|
|
336
|
-
- Observability: `docs/observability/README.md`
|
|
337
|
-
- Cookbook (Patterns): `docs/cookbook/README.md`
|
|
338
|
-
- Architecture: `docs/architecture/README.md`
|
|
339
|
-
- API Reference: `docs/api-reference/README.md`
|
|
340
|
-
- ADR Index: `docs/adr/README.md`
|
|
328
|
+
- Getting Started: `docs/getting-started.md`
|
|
329
|
+
- User Guide: `docs/user-guide/README.md`
|
|
330
|
+
- Advanced Features: `docs/advanced-features/README.md`
|
|
331
|
+
- Observability: `docs/observability/README.md`
|
|
332
|
+
- Cookbook (Patterns): `docs/cookbook/README.md`
|
|
333
|
+
- Architecture: `docs/architecture/README.md`
|
|
334
|
+
- API Reference: `docs/api-reference/README.md`
|
|
335
|
+
- ADR Index: `docs/adr/README.md`
|
|
341
336
|
|
|
342
337
|
-----
|
|
343
338
|
|
|
@@ -359,3 +354,4 @@ See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.
|
|
|
359
354
|
## 📜 License
|
|
360
355
|
|
|
361
356
|
MIT — [LICENSE](https://opensource.org/licenses/MIT)
|
|
357
|
+
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
pico_ioc/__init__.py,sha256=
|
|
2
|
-
pico_ioc/_version.py,sha256
|
|
1
|
+
pico_ioc/__init__.py,sha256=MDneoBBB0JHD_o0_xzkOUeW4e3XjDJSaGMNTb5miBqw,2453
|
|
2
|
+
pico_ioc/_version.py,sha256=-7agX4LcQ0956RU4mY0EYyxYQU9llH7JklZOsqoujdU,22
|
|
3
3
|
pico_ioc/analysis.py,sha256=cxlg3F3bwRIb-kVIqHp1RLYj-sk-GuIgCChE-pf0a7c,4189
|
|
4
|
-
pico_ioc/aop.py,sha256=
|
|
5
|
-
pico_ioc/api.py,sha256=
|
|
6
|
-
pico_ioc/component_scanner.py,sha256=
|
|
4
|
+
pico_ioc/aop.py,sha256=cD4f-xwWOn93XE0q_J5LNepocimd_FOUKT2IABa8erU,14548
|
|
5
|
+
pico_ioc/api.py,sha256=KlZjI_4pJfD_1T32DgmOykii6L1V-5Xtcynd5d57YIA,6400
|
|
6
|
+
pico_ioc/component_scanner.py,sha256=rRZSQbJ7SkssJ5SJBr_m2ih-lpFvVnDQpt-JrYH5Xsg,8999
|
|
7
7
|
pico_ioc/config_builder.py,sha256=7kcYIq1Yrb46Tic7uLeaCDvLA-Sa_p1PIoGF00mivso,2848
|
|
8
8
|
pico_ioc/config_registrar.py,sha256=34iNQY1TUEPTXbb-QV1T-c5VKAn18hBcNt5MLhzDSfY,8456
|
|
9
9
|
pico_ioc/config_runtime.py,sha256=qdPIbGMXOf6KWMM7cva6W-hhbhybEY0swZN01R1emCg,12756
|
|
10
10
|
pico_ioc/constants.py,sha256=AhIt0ieDZ9Turxb_YbNzj11wUbBbzjKfWh1BDlSx2Nw,183
|
|
11
|
-
pico_ioc/container.py,sha256=
|
|
11
|
+
pico_ioc/container.py,sha256=scg0EvYubvWM8NkaYag6MrmOZUfeDhOg7Vq0pAdnWA4,21510
|
|
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=NSfmFPX6Zm2OmMJz16gJFYMhh65iI0n9UlC9M8GmO0c,8428
|
|
@@ -16,10 +16,10 @@ pico_ioc/exceptions.py,sha256=FBuajj5g29hAGODt2tAWuy2sG5mQojdSddaqFzim-aY,2383
|
|
|
16
16
|
pico_ioc/factory.py,sha256=oJXx_BYJuvV8oxYzs5I3gx9WM6uLYZ8GCc43gukNanc,1671
|
|
17
17
|
pico_ioc/locator.py,sha256=JD6psgdGGsBoCwov-G76BrmTfKUoJ22sdwa6wVdmQV8,5064
|
|
18
18
|
pico_ioc/provider_selector.py,sha256=pU7NbI5vifvUlJEjlRJmvveQUZVD47T24QmiP0CHRw0,1213
|
|
19
|
-
pico_ioc/registrar.py,sha256=
|
|
20
|
-
pico_ioc/scope.py,sha256=
|
|
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.
|
|
19
|
+
pico_ioc/registrar.py,sha256=0abgnJMJrEvEyqsvxBNTi6wl0iJHHjSaw75IuqGk56c,8531
|
|
20
|
+
pico_ioc/scope.py,sha256=0Jb8o_yjnbZR1vJdzTvuMHf5fy0Sq3q8O5A16hb1pbE,6765
|
|
21
|
+
pico_ioc-2.2.1.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
|
|
22
|
+
pico_ioc-2.2.1.dist-info/METADATA,sha256=ZIhyfQH8ALbe1O8DodGebB1e_zMP38NvmQ18V8MUsl4,12869
|
|
23
|
+
pico_ioc-2.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
pico_ioc-2.2.1.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
|
|
25
|
+
pico_ioc-2.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|