pico-ioc 1.4.0__py3-none-any.whl → 2.0.0__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 +91 -45
- pico_ioc/_version.py +1 -1
- pico_ioc/aop.py +247 -0
- pico_ioc/api.py +791 -207
- pico_ioc/config_runtime.py +289 -0
- pico_ioc/constants.py +10 -0
- pico_ioc/container.py +289 -189
- pico_ioc/event_bus.py +224 -0
- pico_ioc/exceptions.py +66 -0
- pico_ioc/factory.py +48 -0
- pico_ioc/locator.py +53 -0
- pico_ioc/scope.py +106 -40
- pico_ioc-2.0.0.dist-info/METADATA +230 -0
- pico_ioc-2.0.0.dist-info/RECORD +17 -0
- pico_ioc/_state.py +0 -75
- pico_ioc/builder.py +0 -294
- pico_ioc/config.py +0 -332
- pico_ioc/decorators.py +0 -158
- pico_ioc/interceptors.py +0 -56
- pico_ioc/plugins.py +0 -28
- pico_ioc/policy.py +0 -245
- pico_ioc/proxy.py +0 -129
- pico_ioc/public_api.py +0 -76
- pico_ioc/resolver.py +0 -132
- pico_ioc/scanner.py +0 -203
- pico_ioc/utils.py +0 -25
- pico_ioc-1.4.0.dist-info/METADATA +0 -241
- pico_ioc-1.4.0.dist-info/RECORD +0 -22
- {pico_ioc-1.4.0.dist-info → pico_ioc-2.0.0.dist-info}/WHEEL +0 -0
- {pico_ioc-1.4.0.dist-info → pico_ioc-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-1.4.0.dist-info → pico_ioc-2.0.0.dist-info}/top_level.txt +0 -0
pico_ioc/container.py
CHANGED
|
@@ -1,205 +1,305 @@
|
|
|
1
1
|
# src/pico_ioc/container.py
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
2
|
import inspect
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from . import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def bind(self, key: Any, provider, *, lazy: bool, tags: tuple[str, ...] = ()):
|
|
19
|
-
self._c.bind(key, provider, lazy=lazy, tags=tags)
|
|
20
|
-
|
|
21
|
-
def has(self, key: Any) -> bool:
|
|
22
|
-
return self._c.has(key)
|
|
23
|
-
|
|
24
|
-
def get(self, key: Any):
|
|
25
|
-
return self._c.get(key)
|
|
26
|
-
|
|
3
|
+
import contextvars
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple, overload, Union
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from .constants import LOGGER, PICO_META
|
|
7
|
+
from .exceptions import CircularDependencyError, ComponentCreationError, ProviderNotFoundError
|
|
8
|
+
from .factory import ComponentFactory
|
|
9
|
+
from .locator import ComponentLocator
|
|
10
|
+
from .scope import ScopedCaches, ScopeManager
|
|
11
|
+
from .aop import UnifiedComponentProxy, ContainerObserver
|
|
12
|
+
|
|
13
|
+
KeyT = Union[str, type]
|
|
14
|
+
_resolve_chain: contextvars.ContextVar[Tuple[KeyT, ...]] = contextvars.ContextVar("pico_resolve_chain", default=())
|
|
27
15
|
|
|
28
16
|
class PicoContainer:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
17
|
+
_container_id_var: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("pico_container_id", default=None)
|
|
18
|
+
_container_registry: Dict[str, "PicoContainer"] = {}
|
|
19
|
+
|
|
20
|
+
class _Ctx:
|
|
21
|
+
def __init__(self, container_id: str, profiles: Tuple[str, ...], created_at: float) -> None:
|
|
22
|
+
self.container_id = container_id
|
|
23
|
+
self.profiles = profiles
|
|
24
|
+
self.created_at = created_at
|
|
25
|
+
self.resolve_count = 0
|
|
26
|
+
self.cache_hit_count = 0
|
|
27
|
+
|
|
28
|
+
def __init__(self, component_factory: ComponentFactory, caches: ScopedCaches, scopes: ScopeManager, observers: Optional[List["ContainerObserver"]] = None, container_id: Optional[str] = None, profiles: Tuple[str, ...] = ()) -> None:
|
|
29
|
+
self._factory = component_factory
|
|
30
|
+
self._caches = caches
|
|
31
|
+
self.scopes = scopes
|
|
32
|
+
self._locator: Optional[ComponentLocator] = None
|
|
33
|
+
self._observers = list(observers or [])
|
|
34
|
+
self.container_id = container_id or self._generate_container_id()
|
|
35
|
+
import time as _t
|
|
36
|
+
self.context = PicoContainer._Ctx(container_id=self.container_id, profiles=profiles, created_at=_t.time())
|
|
37
|
+
PicoContainer._container_registry[self.container_id] = self
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _generate_container_id() -> str:
|
|
41
|
+
import time as _t, random as _r
|
|
42
|
+
return f"c{_t.time_ns():x}{_r.randrange(1<<16):04x}"
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def get_current(cls) -> Optional["PicoContainer"]:
|
|
46
|
+
cid = cls._container_id_var.get()
|
|
47
|
+
return cls._container_registry.get(cid) if cid else None
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get_current_id(cls) -> Optional[str]:
|
|
51
|
+
return cls._container_id_var.get()
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def all_containers(cls) -> Dict[str, "PicoContainer"]:
|
|
55
|
+
return dict(cls._container_registry)
|
|
56
|
+
|
|
57
|
+
def activate(self) -> contextvars.Token:
|
|
58
|
+
return PicoContainer._container_id_var.set(self.container_id)
|
|
59
|
+
|
|
60
|
+
def deactivate(self, token: contextvars.Token) -> None:
|
|
61
|
+
PicoContainer._container_id_var.reset(token)
|
|
62
|
+
|
|
63
|
+
@contextmanager
|
|
64
|
+
def as_current(self):
|
|
65
|
+
token = self.activate()
|
|
61
66
|
try:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
67
|
+
yield self
|
|
68
|
+
finally:
|
|
69
|
+
self.deactivate(token)
|
|
70
|
+
|
|
71
|
+
def attach_locator(self, locator: ComponentLocator) -> None:
|
|
72
|
+
self._locator = locator
|
|
73
|
+
|
|
74
|
+
def _cache_for(self, key: KeyT):
|
|
75
|
+
md = self._locator._metadata.get(key) if self._locator else None
|
|
76
|
+
sc = (md.scope if md else "singleton")
|
|
77
|
+
return self._caches.for_scope(self.scopes, sc)
|
|
78
|
+
|
|
79
|
+
def has(self, key: KeyT) -> bool:
|
|
80
|
+
cache = self._cache_for(key)
|
|
81
|
+
return cache.get(key) is not None or self._factory.has(key)
|
|
82
|
+
|
|
83
|
+
@overload
|
|
84
|
+
def get(self, key: type) -> Any: ...
|
|
85
|
+
@overload
|
|
86
|
+
def get(self, key: str) -> Any: ...
|
|
87
|
+
def get(self, key: KeyT) -> Any:
|
|
88
|
+
cache = self._cache_for(key)
|
|
89
|
+
cached = cache.get(key)
|
|
90
|
+
if cached is not None:
|
|
91
|
+
self.context.cache_hit_count += 1
|
|
92
|
+
for o in self._observers: o.on_cache_hit(key)
|
|
93
|
+
return cached
|
|
94
|
+
import time as _tm
|
|
95
|
+
t0 = _tm.perf_counter()
|
|
96
|
+
chain = list(_resolve_chain.get())
|
|
97
|
+
for k in chain:
|
|
98
|
+
if k == key:
|
|
99
|
+
raise CircularDependencyError(chain, key)
|
|
100
|
+
token_chain = _resolve_chain.set(tuple(chain + [key]))
|
|
101
|
+
token_container = self.activate()
|
|
102
|
+
try:
|
|
103
|
+
if not self._factory.has(key):
|
|
104
|
+
alt = None
|
|
105
|
+
if isinstance(key, type):
|
|
106
|
+
alt = self._resolve_type_key(key)
|
|
107
|
+
elif isinstance(key, str) and self._locator:
|
|
108
|
+
for k, md in self._locator._metadata.items():
|
|
109
|
+
if md.pico_name == key:
|
|
110
|
+
alt = k
|
|
111
|
+
break
|
|
112
|
+
if alt is not None:
|
|
113
|
+
self._factory.bind(key, lambda a=alt: self.get(a))
|
|
114
|
+
provider = self._factory.get(key)
|
|
86
115
|
try:
|
|
87
|
-
|
|
88
|
-
except
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
instance = provider()
|
|
117
|
+
except ProviderNotFoundError as e:
|
|
118
|
+
raise
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise ComponentCreationError(key, e)
|
|
121
|
+
instance = self._maybe_wrap_with_aspects(key, instance)
|
|
122
|
+
cache.put(key, instance)
|
|
123
|
+
self.context.resolve_count += 1
|
|
124
|
+
took_ms = (_tm.perf_counter() - t0) * 1000
|
|
125
|
+
for o in self._observers: o.on_resolve(key, took_ms)
|
|
126
|
+
return instance
|
|
127
|
+
finally:
|
|
128
|
+
_resolve_chain.reset(token_chain)
|
|
129
|
+
self.deactivate(token_container)
|
|
130
|
+
|
|
131
|
+
async def aget(self, key: KeyT) -> Any:
|
|
132
|
+
cache = self._cache_for(key)
|
|
133
|
+
cached = cache.get(key)
|
|
134
|
+
if cached is not None:
|
|
135
|
+
self.context.cache_hit_count += 1
|
|
136
|
+
for o in self._observers: o.on_cache_hit(key)
|
|
137
|
+
return cached
|
|
138
|
+
import time as _tm
|
|
139
|
+
t0 = _tm.perf_counter()
|
|
140
|
+
chain = list(_resolve_chain.get())
|
|
141
|
+
for k in chain:
|
|
142
|
+
if k == key:
|
|
143
|
+
raise CircularDependencyError(chain, key)
|
|
144
|
+
token_chain = _resolve_chain.set(tuple(chain + [key]))
|
|
145
|
+
token_container = self.activate()
|
|
92
146
|
try:
|
|
147
|
+
if not self._factory.has(key):
|
|
148
|
+
alt = None
|
|
149
|
+
if isinstance(key, type):
|
|
150
|
+
alt = self._resolve_type_key(key)
|
|
151
|
+
elif isinstance(key, str) and self._locator:
|
|
152
|
+
for k, md in self._locator._metadata.items():
|
|
153
|
+
if md.pico_name == key:
|
|
154
|
+
alt = k
|
|
155
|
+
break
|
|
156
|
+
if alt is not None:
|
|
157
|
+
self._factory.bind(key, lambda a=alt: self.get(a))
|
|
158
|
+
provider = self._factory.get(key)
|
|
93
159
|
try:
|
|
94
|
-
instance =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
ci.on_exception(key, exc)
|
|
99
|
-
except Exception:
|
|
100
|
-
pass
|
|
160
|
+
instance = provider()
|
|
161
|
+
if inspect.isawaitable(instance):
|
|
162
|
+
instance = await instance
|
|
163
|
+
except ProviderNotFoundError as e:
|
|
101
164
|
raise
|
|
165
|
+
except Exception as e:
|
|
166
|
+
raise ComponentCreationError(key, e)
|
|
167
|
+
instance = self._maybe_wrap_with_aspects(key, instance)
|
|
168
|
+
cache.put(key, instance)
|
|
169
|
+
self.context.resolve_count += 1
|
|
170
|
+
took_ms = (_tm.perf_counter() - t0) * 1000
|
|
171
|
+
for o in self._observers: o.on_resolve(key, took_ms)
|
|
172
|
+
return instance
|
|
102
173
|
finally:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
174
|
+
_resolve_chain.reset(token_chain)
|
|
175
|
+
self.deactivate(token_container)
|
|
176
|
+
|
|
177
|
+
def _resolve_type_key(self, key: type):
|
|
178
|
+
if not self._locator:
|
|
179
|
+
return None
|
|
180
|
+
cands: List[Tuple[bool, Any]] = []
|
|
181
|
+
for k, md in self._locator._metadata.items():
|
|
182
|
+
typ = md.provided_type or md.concrete_class
|
|
183
|
+
if not isinstance(typ, type):
|
|
184
|
+
continue
|
|
109
185
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
instance = maybe
|
|
186
|
+
if typ is not key and issubclass(typ, key):
|
|
187
|
+
cands.append((md.primary, k))
|
|
113
188
|
except Exception:
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
self._singletons[key] = instance
|
|
117
|
-
return instance
|
|
118
|
-
|
|
119
|
-
# --- lifecycle ---
|
|
120
|
-
|
|
121
|
-
def eager_instantiate_all(self):
|
|
122
|
-
for key, prov in list(self._providers.items()):
|
|
123
|
-
if not prov["lazy"]:
|
|
124
|
-
self.get(key)
|
|
125
|
-
|
|
126
|
-
# --- helpers for multiples ---
|
|
127
|
-
|
|
128
|
-
def get_all(self, base_type: Any):
|
|
129
|
-
return tuple(self._resolve_all_for_base(base_type, qualifiers=()))
|
|
130
|
-
|
|
131
|
-
def get_all_qualified(self, base_type: Any, *qualifiers: str):
|
|
132
|
-
return tuple(self._resolve_all_for_base(base_type, qualifiers=qualifiers))
|
|
133
|
-
|
|
134
|
-
def _resolve_all_for_base(self, base_type: Any, qualifiers=()):
|
|
135
|
-
matches = []
|
|
136
|
-
for provider_key, meta in self._providers.items():
|
|
137
|
-
cls = provider_key if isinstance(provider_key, type) else None
|
|
138
|
-
if cls is None:
|
|
139
|
-
continue
|
|
140
|
-
if _requires_collection_of_base(cls, base_type):
|
|
141
189
|
continue
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _is_protocol(t) -> bool:
|
|
156
|
-
return getattr(t, "_is_protocol", False) is True
|
|
157
|
-
|
|
190
|
+
if not cands:
|
|
191
|
+
return None
|
|
192
|
+
prim = [k for is_p, k in cands if is_p]
|
|
193
|
+
return prim[0] if prim else cands[0][1]
|
|
194
|
+
|
|
195
|
+
def _maybe_wrap_with_aspects(self, key, instance: Any) -> Any:
|
|
196
|
+
if isinstance(instance, UnifiedComponentProxy):
|
|
197
|
+
return instance
|
|
198
|
+
cls = type(instance)
|
|
199
|
+
for _, fn in inspect.getmembers(cls, predicate=lambda m: inspect.isfunction(m) or inspect.ismethod(m) or inspect.iscoroutinefunction(m)):
|
|
200
|
+
if getattr(fn, "_pico_interceptors_", None):
|
|
201
|
+
return UnifiedComponentProxy(container=self, target=instance)
|
|
202
|
+
return instance
|
|
158
203
|
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
def cleanup_all(self) -> None:
|
|
205
|
+
for _, obj in self._caches.all_items():
|
|
206
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
207
|
+
meta = getattr(m, PICO_META, {})
|
|
208
|
+
if meta.get("cleanup", False):
|
|
209
|
+
from .api import _resolve_args
|
|
210
|
+
kwargs = _resolve_args(m, self)
|
|
211
|
+
m(**kwargs)
|
|
212
|
+
if self._locator:
|
|
213
|
+
seen = set()
|
|
214
|
+
for md in self._locator._metadata.values():
|
|
215
|
+
fc = md.factory_class
|
|
216
|
+
if fc and fc not in seen:
|
|
217
|
+
seen.add(fc)
|
|
218
|
+
inst = self.get(fc) if self._factory.has(fc) else fc()
|
|
219
|
+
for _, m in inspect.getmembers(inst, predicate=inspect.ismethod):
|
|
220
|
+
meta = getattr(m, PICO_META, {})
|
|
221
|
+
if meta.get("cleanup", False):
|
|
222
|
+
from .api import _resolve_args
|
|
223
|
+
kwargs = _resolve_args(m, self)
|
|
224
|
+
m(**kwargs)
|
|
225
|
+
|
|
226
|
+
def activate_scope(self, name: str, scope_id: Any):
|
|
227
|
+
return self.scopes.activate(name, scope_id)
|
|
228
|
+
|
|
229
|
+
def deactivate_scope(self, name: str, token: Optional[contextvars.Token]) -> None:
|
|
230
|
+
self.scopes.deactivate(name, token)
|
|
231
|
+
|
|
232
|
+
def info(self, msg: str) -> None:
|
|
233
|
+
LOGGER.info(f"[{self.container_id[:8]}] {msg}")
|
|
234
|
+
|
|
235
|
+
@contextmanager
|
|
236
|
+
def scope(self, name: str, scope_id: Any):
|
|
237
|
+
tok = self.activate_scope(name, scope_id)
|
|
238
|
+
try:
|
|
239
|
+
yield self
|
|
240
|
+
finally:
|
|
241
|
+
self.deactivate_scope(name, tok)
|
|
165
242
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
243
|
+
def health_check(self) -> Dict[str, bool]:
|
|
244
|
+
out: Dict[str, bool] = {}
|
|
245
|
+
for k, obj in self._caches.all_items():
|
|
246
|
+
for name, m in inspect.getmembers(obj, predicate=callable):
|
|
247
|
+
if getattr(m, PICO_META, {}).get("health_check", False):
|
|
248
|
+
try:
|
|
249
|
+
out[f"{getattr(k,'__name__',k)}.{name}"] = bool(m())
|
|
250
|
+
except Exception:
|
|
251
|
+
out[f"{getattr(k,'__name__',k)}.{name}"] = False
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
async def cleanup_all_async(self) -> None:
|
|
255
|
+
for _, obj in self._caches.all_items():
|
|
256
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
257
|
+
meta = getattr(m, PICO_META, {})
|
|
258
|
+
if meta.get("cleanup", False):
|
|
259
|
+
from .api import _resolve_args
|
|
260
|
+
res = m(**_resolve_args(m, self))
|
|
261
|
+
import inspect as _i
|
|
262
|
+
if _i.isawaitable(res):
|
|
263
|
+
await res
|
|
264
|
+
if self._locator:
|
|
265
|
+
seen = set()
|
|
266
|
+
for md in self._locator._metadata.values():
|
|
267
|
+
fc = md.factory_class
|
|
268
|
+
if fc and fc not in seen:
|
|
269
|
+
seen.add(fc)
|
|
270
|
+
inst = self.get(fc) if self._factory.has(fc) else fc()
|
|
271
|
+
for _, m in inspect.getmembers(inst, predicate=inspect.ismethod):
|
|
272
|
+
meta = getattr(m, PICO_META, {})
|
|
273
|
+
if meta.get("cleanup", False):
|
|
274
|
+
from .api import _resolve_args
|
|
275
|
+
res = m(**_resolve_args(m, self))
|
|
276
|
+
import inspect as _i
|
|
277
|
+
if _i.isawaitable(res):
|
|
278
|
+
await res
|
|
279
|
+
try:
|
|
280
|
+
from .event_bus import EventBus
|
|
281
|
+
for _, obj in self._caches.all_items():
|
|
282
|
+
if isinstance(obj, EventBus):
|
|
283
|
+
await obj.aclose()
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
def stats(self) -> Dict[str, Any]:
|
|
288
|
+
import time as _t
|
|
289
|
+
resolves = self.context.resolve_count
|
|
290
|
+
hits = self.context.cache_hit_count
|
|
291
|
+
total = resolves + hits
|
|
292
|
+
return {
|
|
293
|
+
"container_id": self.container_id,
|
|
294
|
+
"profiles": self.context.profiles,
|
|
295
|
+
"uptime_seconds": _t.time() - self.context.created_at,
|
|
296
|
+
"total_resolves": resolves,
|
|
297
|
+
"cache_hits": hits,
|
|
298
|
+
"cache_hit_rate": (hits / total) if total > 0 else 0.0,
|
|
299
|
+
"registered_components": len(self._locator._metadata) if self._locator else 0,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
def shutdown(self) -> None:
|
|
303
|
+
self.cleanup_all()
|
|
304
|
+
PicoContainer._container_registry.pop(self.container_id, None)
|
|
205
305
|
|