pico-ioc 2.1.2__py3-none-any.whl → 2.2.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 +2 -1
- pico_ioc/_version.py +1 -1
- pico_ioc/analysis.py +3 -4
- pico_ioc/aop.py +52 -17
- pico_ioc/api.py +9 -3
- pico_ioc/component_scanner.py +48 -24
- pico_ioc/config_runtime.py +5 -2
- pico_ioc/container.py +20 -6
- pico_ioc/event_bus.py +22 -19
- pico_ioc/registrar.py +5 -7
- pico_ioc/scope.py +22 -16
- {pico_ioc-2.1.2.dist-info → pico_ioc-2.2.0.dist-info}/METADATA +52 -47
- pico_ioc-2.2.0.dist-info/RECORD +25 -0
- pico_ioc-2.1.2.dist-info/RECORD +0 -25
- {pico_ioc-2.1.2.dist-info → pico_ioc-2.2.0.dist-info}/WHEEL +0 -0
- {pico_ioc-2.1.2.dist-info → pico_ioc-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.1.2.dist-info → pico_ioc-2.2.0.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
|
+
__version__ = '2.2.0'
|
pico_ioc/analysis.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import (
|
|
|
8
8
|
Dict, Mapping
|
|
9
9
|
)
|
|
10
10
|
from .decorators import Qualifier
|
|
11
|
+
from .constants import LOGGER
|
|
11
12
|
|
|
12
13
|
KeyT = Union[str, type]
|
|
13
14
|
|
|
@@ -47,20 +48,18 @@ def _check_optional(ann: Any) -> Tuple[Any, bool]:
|
|
|
47
48
|
def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[DependencyRequest, ...]:
|
|
48
49
|
try:
|
|
49
50
|
sig = inspect.signature(callable_obj)
|
|
50
|
-
except (ValueError, TypeError):
|
|
51
|
+
except (ValueError, TypeError) as e:
|
|
52
|
+
LOGGER.debug(f"Could not analyze dependencies for {callable_obj!r}: {e}")
|
|
51
53
|
return ()
|
|
52
54
|
|
|
53
55
|
plan: List[DependencyRequest] = []
|
|
54
56
|
|
|
55
57
|
SUPPORTED_COLLECTION_ORIGINS = (
|
|
56
|
-
# Runtime types
|
|
57
58
|
list,
|
|
58
59
|
set,
|
|
59
60
|
tuple,
|
|
60
61
|
frozenset,
|
|
61
62
|
collections.deque,
|
|
62
|
-
|
|
63
|
-
# Typing ABCs (from get_origin)
|
|
64
63
|
collections.abc.Iterable,
|
|
65
64
|
collections.abc.Collection,
|
|
66
65
|
collections.abc.Sequence,
|
pico_ioc/aop.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# src/pico_ioc/aop.py
|
|
2
|
-
|
|
3
1
|
import inspect
|
|
4
2
|
import pickle
|
|
5
3
|
import threading
|
|
@@ -75,15 +73,18 @@ def health(fn):
|
|
|
75
73
|
return fn
|
|
76
74
|
|
|
77
75
|
class UnifiedComponentProxy:
|
|
78
|
-
__slots__ = ("_target", "_creator", "_container", "_cache", "_lock")
|
|
79
|
-
|
|
76
|
+
__slots__ = ("_target", "_creator", "_container", "_cache", "_lock", "_component_key")
|
|
77
|
+
|
|
78
|
+
def __init__(self, *, container: Any, target: Any = None, object_creator: Callable[[], Any] | None = None, component_key: Any = None):
|
|
80
79
|
if container is None:
|
|
81
80
|
raise ValueError("UnifiedComponentProxy requires a non-null container")
|
|
82
81
|
if target is None and object_creator is None:
|
|
83
82
|
raise ValueError("UnifiedComponentProxy requires either a target or an object_creator")
|
|
83
|
+
|
|
84
84
|
object.__setattr__(self, "_container", container)
|
|
85
85
|
object.__setattr__(self, "_target", target)
|
|
86
86
|
object.__setattr__(self, "_creator", object_creator)
|
|
87
|
+
object.__setattr__(self, "_component_key", component_key)
|
|
87
88
|
object.__setattr__(self, "_cache", {})
|
|
88
89
|
object.__setattr__(self, "_lock", threading.RLock())
|
|
89
90
|
|
|
@@ -98,6 +99,7 @@ class UnifiedComponentProxy:
|
|
|
98
99
|
def __setstate__(self, state):
|
|
99
100
|
object.__setattr__(self, "_container", None)
|
|
100
101
|
object.__setattr__(self, "_creator", None)
|
|
102
|
+
object.__setattr__(self, "_component_key", None)
|
|
101
103
|
object.__setattr__(self, "_cache", {})
|
|
102
104
|
object.__setattr__(self, "_lock", threading.RLock())
|
|
103
105
|
try:
|
|
@@ -110,14 +112,17 @@ class UnifiedComponentProxy:
|
|
|
110
112
|
tgt = object.__getattribute__(self, "_target")
|
|
111
113
|
if tgt is not None:
|
|
112
114
|
return tgt
|
|
115
|
+
|
|
113
116
|
lock = object.__getattribute__(self, "_lock")
|
|
114
117
|
with lock:
|
|
115
118
|
tgt = object.__getattribute__(self, "_target")
|
|
116
119
|
if tgt is not None:
|
|
117
120
|
return tgt
|
|
121
|
+
|
|
118
122
|
creator = object.__getattribute__(self, "_creator")
|
|
119
123
|
if not callable(creator):
|
|
120
124
|
raise TypeError("UnifiedComponentProxy object_creator must be callable")
|
|
125
|
+
|
|
121
126
|
tgt = creator()
|
|
122
127
|
if tgt is None:
|
|
123
128
|
raise RuntimeError("UnifiedComponentProxy object_creator returned None")
|
|
@@ -128,27 +133,51 @@ class UnifiedComponentProxy:
|
|
|
128
133
|
if inspect.isawaitable(res):
|
|
129
134
|
raise AsyncResolutionError(
|
|
130
135
|
f"Lazy component {type(tgt).__name__} requires async "
|
|
131
|
-
"@configure but was resolved via sync
|
|
136
|
+
"@configure but was resolved via sync access (proxy __getattr__). "
|
|
137
|
+
"Use 'await container.aget(Component)' to force initialization."
|
|
132
138
|
)
|
|
133
139
|
|
|
134
140
|
object.__setattr__(self, "_target", tgt)
|
|
135
141
|
return tgt
|
|
142
|
+
|
|
143
|
+
async def _async_init_if_needed(self) -> None:
|
|
144
|
+
if object.__getattribute__(self, "_target") is not None:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
lock = object.__getattribute__(self, "_lock")
|
|
148
|
+
tgt = object.__getattribute__(self, "_target")
|
|
149
|
+
if tgt is not None:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
creator = object.__getattribute__(self, "_creator")
|
|
153
|
+
container = object.__getattribute__(self, "_container")
|
|
154
|
+
|
|
155
|
+
tgt = creator()
|
|
156
|
+
|
|
157
|
+
if container and hasattr(container, "_run_configure_methods"):
|
|
158
|
+
res = container._run_configure_methods(tgt)
|
|
159
|
+
if inspect.isawaitable(res):
|
|
160
|
+
await res
|
|
161
|
+
|
|
162
|
+
with lock:
|
|
163
|
+
object.__setattr__(self, "_target", tgt)
|
|
136
164
|
|
|
137
165
|
def _scope_signature(self) -> Tuple[Any, ...]:
|
|
138
166
|
container = object.__getattribute__(self, "_container")
|
|
139
|
-
|
|
167
|
+
key = object.__getattribute__(self, "_component_key")
|
|
140
168
|
loc = getattr(container, "_locator", None)
|
|
141
|
-
|
|
169
|
+
|
|
170
|
+
if not loc or key is None:
|
|
142
171
|
return ()
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
172
|
+
|
|
173
|
+
if key in loc._metadata:
|
|
174
|
+
md = loc._metadata[key]
|
|
175
|
+
sc = md.scope
|
|
176
|
+
if sc == "singleton":
|
|
177
|
+
return ()
|
|
178
|
+
|
|
179
|
+
return (container.scopes.get_id(sc),)
|
|
180
|
+
|
|
152
181
|
return ()
|
|
153
182
|
|
|
154
183
|
def _build_wrapped(self, name: str, bound: Callable[..., Any], interceptors_cls: Tuple[type, ...]):
|
|
@@ -212,6 +241,7 @@ class UnifiedComponentProxy:
|
|
|
212
241
|
lock = object.__getattribute__(self, "_lock")
|
|
213
242
|
with lock:
|
|
214
243
|
cache: Dict[str, Tuple[Tuple[Any, ...], Callable[..., Any], Tuple[type, ...]]] = object.__getattribute__(self, "_cache")
|
|
244
|
+
|
|
215
245
|
cur_sig = self._scope_signature()
|
|
216
246
|
cached = cache.get(name)
|
|
217
247
|
|
|
@@ -224,7 +254,12 @@ class UnifiedComponentProxy:
|
|
|
224
254
|
cache[name] = (sig, wrapped, cls_tuple)
|
|
225
255
|
return wrapped
|
|
226
256
|
|
|
227
|
-
def __setattr__(self, name, value):
|
|
257
|
+
def __setattr__(self, name, value):
|
|
258
|
+
if name in ("_target", "_creator", "_container", "_cache", "_lock", "_component_key"):
|
|
259
|
+
object.__setattr__(self, name, value)
|
|
260
|
+
else:
|
|
261
|
+
setattr(self._get_real_object(), name, value)
|
|
262
|
+
|
|
228
263
|
def __delattr__(self, name): delattr(self._get_real_object(), name)
|
|
229
264
|
def __str__(self): return str(self._get_real_object())
|
|
230
265
|
def __repr__(self): return repr(self._get_real_object())
|
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/config_runtime.py
CHANGED
|
@@ -305,8 +305,11 @@ class ObjectGraphBuilder:
|
|
|
305
305
|
if t is int:
|
|
306
306
|
if isinstance(node, int):
|
|
307
307
|
return node
|
|
308
|
-
if isinstance(node, str)
|
|
309
|
-
|
|
308
|
+
if isinstance(node, str):
|
|
309
|
+
try:
|
|
310
|
+
return int(node.strip())
|
|
311
|
+
except ValueError:
|
|
312
|
+
pass
|
|
310
313
|
raise ConfigurationError(f"Expected int at {'.'.join(path)}")
|
|
311
314
|
if t is float:
|
|
312
315
|
if isinstance(node, (int, float)):
|
pico_ioc/container.py
CHANGED
|
@@ -205,7 +205,11 @@ class PicoContainer:
|
|
|
205
205
|
args = self._resolve_args(configure_deps)
|
|
206
206
|
res = m(**args)
|
|
207
207
|
if inspect.isawaitable(res):
|
|
208
|
-
|
|
208
|
+
raise AsyncResolutionError(
|
|
209
|
+
f"Component {type(instance).__name__} returned an awaitable from synchronous "
|
|
210
|
+
f"@configure method '{m.__name__}'. You must use 'await container.aget()' "
|
|
211
|
+
"or make the method synchronous."
|
|
212
|
+
)
|
|
209
213
|
return instance
|
|
210
214
|
|
|
211
215
|
async def runner():
|
|
@@ -252,10 +256,12 @@ class PicoContainer:
|
|
|
252
256
|
async def aget(self, key: KeyT) -> Any:
|
|
253
257
|
instance_or_awaitable, took_ms, was_cached = self._resolve_or_create_internal(key)
|
|
254
258
|
|
|
259
|
+
instance = instance_or_awaitable
|
|
255
260
|
if was_cached:
|
|
256
|
-
|
|
261
|
+
if isinstance(instance, UnifiedComponentProxy):
|
|
262
|
+
await instance._async_init_if_needed()
|
|
263
|
+
return instance
|
|
257
264
|
|
|
258
|
-
instance = instance_or_awaitable
|
|
259
265
|
if inspect.isawaitable(instance_or_awaitable):
|
|
260
266
|
instance = await instance_or_awaitable
|
|
261
267
|
|
|
@@ -269,6 +275,10 @@ class PicoContainer:
|
|
|
269
275
|
instance = instance_or_awaitable_configured
|
|
270
276
|
|
|
271
277
|
final_instance = self._maybe_wrap_with_aspects(key, instance)
|
|
278
|
+
|
|
279
|
+
if isinstance(final_instance, UnifiedComponentProxy):
|
|
280
|
+
await final_instance._async_init_if_needed()
|
|
281
|
+
|
|
272
282
|
cache = self._cache_for(key)
|
|
273
283
|
cache.put(key, final_instance)
|
|
274
284
|
self.context.resolve_count += 1
|
|
@@ -282,7 +292,7 @@ class PicoContainer:
|
|
|
282
292
|
cls = type(instance)
|
|
283
293
|
for _, fn in inspect.getmembers(cls, predicate=lambda m: inspect.isfunction(m) or inspect.ismethod(m) or inspect.iscoroutinefunction(m)):
|
|
284
294
|
if getattr(fn, "_pico_interceptors_", None):
|
|
285
|
-
return UnifiedComponentProxy(container=self, target=instance)
|
|
295
|
+
return UnifiedComponentProxy(container=self, target=instance, component_key=key)
|
|
286
296
|
return instance
|
|
287
297
|
|
|
288
298
|
def _iterate_cleanup_targets(self) -> Iterable[Any]:
|
|
@@ -374,7 +384,11 @@ class PicoContainer:
|
|
|
374
384
|
self.cleanup_all()
|
|
375
385
|
PicoContainer._container_registry.pop(self.container_id, None)
|
|
376
386
|
|
|
377
|
-
def
|
|
387
|
+
async def ashutdown(self) -> None:
|
|
388
|
+
await self.cleanup_all_async()
|
|
389
|
+
PicoContainer._container_registry.pop(self.container_id, None)
|
|
390
|
+
|
|
391
|
+
def build_resolution_graph(self):
|
|
378
392
|
return _build_resolution_graph(self._locator)
|
|
379
393
|
|
|
380
394
|
def export_graph(
|
|
@@ -424,7 +438,7 @@ class PicoContainer:
|
|
|
424
438
|
pid = _node_id(parent)
|
|
425
439
|
for child in deps:
|
|
426
440
|
cid = _node_id(child)
|
|
427
|
-
lines.append(f" {pid} -> {
|
|
441
|
+
lines.append(f" {pid} -> {child};")
|
|
428
442
|
|
|
429
443
|
lines.append("}")
|
|
430
444
|
|
pico_ioc/event_bus.py
CHANGED
|
@@ -155,25 +155,28 @@ class EventBus:
|
|
|
155
155
|
raise EventBusClosedError()
|
|
156
156
|
if self._queue is None:
|
|
157
157
|
raise EventBusError("Worker queue not initialized. Call start_worker().")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
158
|
+
|
|
159
|
+
queue_ref = self._queue
|
|
160
|
+
loop_ref = self._worker_loop
|
|
161
|
+
|
|
162
|
+
if loop_ref and loop_ref.is_running():
|
|
163
|
+
try:
|
|
164
|
+
current_loop = asyncio.get_running_loop()
|
|
165
|
+
if current_loop is loop_ref:
|
|
166
|
+
try:
|
|
167
|
+
queue_ref.put_nowait(event)
|
|
168
|
+
return
|
|
169
|
+
except asyncio.QueueFull:
|
|
170
|
+
raise EventBusQueueFullError()
|
|
171
|
+
except RuntimeError:
|
|
172
|
+
pass
|
|
173
|
+
try:
|
|
174
|
+
loop_ref.call_soon_threadsafe(queue_ref.put_nowait, event)
|
|
175
|
+
return
|
|
176
|
+
except asyncio.QueueFull:
|
|
177
|
+
raise EventBusQueueFullError()
|
|
178
|
+
else:
|
|
179
|
+
raise EventBusError("Worker queue not initialized or loop not running. Call start_worker().")
|
|
177
180
|
|
|
178
181
|
async def aclose(self) -> None:
|
|
179
182
|
await self.stop_worker()
|
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))
|
|
@@ -61,20 +63,17 @@ class Registrar:
|
|
|
61
63
|
for key, md in list(self._metadata.items()):
|
|
62
64
|
if md.lazy:
|
|
63
65
|
original = self._factory.get(key, origin='lazy')
|
|
64
|
-
def lazy_proxy_provider(_orig=original, _p=pico):
|
|
65
|
-
return UnifiedComponentProxy(container=_p, object_creator=_orig)
|
|
66
|
+
def lazy_proxy_provider(_orig=original, _p=pico, _k=key):
|
|
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,4 +1,3 @@
|
|
|
1
|
-
# src/pico_ioc/scope.py
|
|
2
1
|
import contextvars
|
|
3
2
|
import inspect
|
|
4
3
|
from typing import Any, Dict, Optional, Tuple
|
|
@@ -93,11 +92,11 @@ class ScopeManager:
|
|
|
93
92
|
return self.signature(self.names())
|
|
94
93
|
|
|
95
94
|
class ScopedCaches:
|
|
96
|
-
def __init__(self
|
|
95
|
+
def __init__(self) -> None:
|
|
97
96
|
self._singleton = ComponentContainer()
|
|
98
|
-
self._by_scope: Dict[str,
|
|
99
|
-
self._max = int(max_scopes_per_type)
|
|
97
|
+
self._by_scope: Dict[str, Dict[Any, ComponentContainer]] = {}
|
|
100
98
|
self._no_cache = _NoCacheContainer()
|
|
99
|
+
|
|
101
100
|
def _cleanup_object(self, obj: Any) -> None:
|
|
102
101
|
try:
|
|
103
102
|
from .constants import PICO_META
|
|
@@ -132,15 +131,19 @@ class ScopedCaches:
|
|
|
132
131
|
return self._singleton
|
|
133
132
|
if scope == "prototype":
|
|
134
133
|
return self._no_cache
|
|
134
|
+
|
|
135
135
|
sid = scopes.get_id(scope)
|
|
136
|
-
|
|
136
|
+
|
|
137
|
+
if sid is None:
|
|
138
|
+
raise ScopeError(
|
|
139
|
+
f"Cannot resolve component in scope '{scope}': No active scope ID found. "
|
|
140
|
+
f"Are you trying to use a {scope}-scoped component outside of its context?"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
bucket = self._by_scope.setdefault(scope, {})
|
|
137
144
|
if sid in bucket:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return c
|
|
141
|
-
if len(bucket) >= self._max:
|
|
142
|
-
_, old = bucket.popitem(last=False)
|
|
143
|
-
self._cleanup_container(old)
|
|
145
|
+
return bucket[sid]
|
|
146
|
+
|
|
144
147
|
c = ComponentContainer()
|
|
145
148
|
bucket[sid] = c
|
|
146
149
|
return c
|
|
@@ -159,8 +162,11 @@ class ScopedCaches:
|
|
|
159
162
|
bucket = self._by_scope.get(scope)
|
|
160
163
|
if not bucket:
|
|
161
164
|
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
|
|
166
|
+
# Manual cleanup if needed, though we rely on explicit cleanup now
|
|
167
|
+
if len(bucket) > keep:
|
|
168
|
+
# Simple eviction strategy if forced manually
|
|
169
|
+
keys_to_remove = list(bucket.keys())[:len(bucket)-keep]
|
|
170
|
+
for k in keys_to_remove:
|
|
171
|
+
container = bucket.pop(k)
|
|
172
|
+
self._cleanup_container(container)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pico-ioc
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -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,11 +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
|
+
```
|
|
123
|
+
|
|
124
|
+
-----
|
|
125
|
+
|
|
126
|
+
### ⚠️ Important Note
|
|
127
|
+
|
|
128
|
+
**Breaking Behavior in Scope Management (v2.1.3+):**
|
|
129
|
+
**Scope LRU Eviction has been removed** to guarantee data integrity.
|
|
130
|
+
|
|
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.
|
|
124
133
|
|
|
125
134
|
-----
|
|
126
135
|
|
|
@@ -238,12 +247,16 @@ async def main():
|
|
|
238
247
|
container = init(modules=[__name__])
|
|
239
248
|
repo = await container.aget(AsyncRepo) # Async resolution
|
|
240
249
|
print(await repo.fetch())
|
|
250
|
+
|
|
251
|
+
# Graceful async shutdown (calls @cleanup async methods)
|
|
252
|
+
await container.ashutdown()
|
|
241
253
|
|
|
242
254
|
asyncio.run(main())
|
|
243
255
|
```
|
|
244
256
|
|
|
245
|
-
- `__ainit__` runs after construction if defined.
|
|
246
|
-
- 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.
|
|
247
260
|
|
|
248
261
|
-----
|
|
249
262
|
|
|
@@ -283,35 +296,26 @@ result = c.get(Demo).work()
|
|
|
283
296
|
print(f"Result: {result}")
|
|
284
297
|
```
|
|
285
298
|
|
|
286
|
-
Output:
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
→ calling Demo.work
|
|
290
|
-
Working...
|
|
291
|
-
← Demo.work done (10.xxms)
|
|
292
|
-
Result: ok
|
|
293
|
-
```
|
|
294
|
-
|
|
295
299
|
-----
|
|
296
300
|
|
|
297
301
|
## 👁️ Observability & Cleanup
|
|
298
302
|
|
|
299
|
-
- Export a dependency graph in DOT format:
|
|
303
|
+
- Export a dependency graph in DOT format:
|
|
300
304
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
f.write(dot)
|
|
306
|
-
```
|
|
305
|
+
```python
|
|
306
|
+
c = init(modules=[...])
|
|
307
|
+
c.export_graph("dependencies.dot") # Writes directly to file
|
|
308
|
+
```
|
|
307
309
|
|
|
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.
|
|
310
|
+
- Health checks:
|
|
311
311
|
|
|
312
|
-
-
|
|
313
|
-
|
|
314
|
-
|
|
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.
|
|
314
|
+
|
|
315
|
+
- Container cleanup:
|
|
316
|
+
|
|
317
|
+
- For sync apps: `container.shutdown()`
|
|
318
|
+
- For async apps: `await container.ashutdown()`
|
|
315
319
|
|
|
316
320
|
Use cleanup in application shutdown hooks to release resources deterministically.
|
|
317
321
|
|
|
@@ -321,14 +325,14 @@ Use cleanup in application shutdown hooks to release resources deterministically
|
|
|
321
325
|
|
|
322
326
|
The full documentation is available within the `docs/` directory of the project repository. Start with `docs/README.md` for navigation.
|
|
323
327
|
|
|
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`
|
|
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`
|
|
332
336
|
|
|
333
337
|
-----
|
|
334
338
|
|
|
@@ -343,10 +347,11 @@ tox
|
|
|
343
347
|
|
|
344
348
|
## 🧾 Changelog
|
|
345
349
|
|
|
346
|
-
See [CHANGELOG.md](
|
|
350
|
+
See [CHANGELOG.md](https://www.google.com/search?q=./CHANGELOG.md) — Significant redesigns and features in v2.0+.
|
|
347
351
|
|
|
348
352
|
-----
|
|
349
353
|
|
|
350
354
|
## 📜 License
|
|
351
355
|
|
|
352
356
|
MIT — [LICENSE](https://opensource.org/licenses/MIT)
|
|
357
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
pico_ioc/__init__.py,sha256=MDneoBBB0JHD_o0_xzkOUeW4e3XjDJSaGMNTb5miBqw,2453
|
|
2
|
+
pico_ioc/_version.py,sha256=Vyf6P6UCZKFeQtRzYujPmFfdlqSfnc01VEMWE3O0ZrA,22
|
|
3
|
+
pico_ioc/analysis.py,sha256=cxlg3F3bwRIb-kVIqHp1RLYj-sk-GuIgCChE-pf0a7c,4189
|
|
4
|
+
pico_ioc/aop.py,sha256=VkmLzoztPbFjLIqBmXohcCNd4w6UC9ZrqYOE8DsMQ7I,14417
|
|
5
|
+
pico_ioc/api.py,sha256=KlZjI_4pJfD_1T32DgmOykii6L1V-5Xtcynd5d57YIA,6400
|
|
6
|
+
pico_ioc/component_scanner.py,sha256=rRZSQbJ7SkssJ5SJBr_m2ih-lpFvVnDQpt-JrYH5Xsg,8999
|
|
7
|
+
pico_ioc/config_builder.py,sha256=7kcYIq1Yrb46Tic7uLeaCDvLA-Sa_p1PIoGF00mivso,2848
|
|
8
|
+
pico_ioc/config_registrar.py,sha256=34iNQY1TUEPTXbb-QV1T-c5VKAn18hBcNt5MLhzDSfY,8456
|
|
9
|
+
pico_ioc/config_runtime.py,sha256=qdPIbGMXOf6KWMM7cva6W-hhbhybEY0swZN01R1emCg,12756
|
|
10
|
+
pico_ioc/constants.py,sha256=AhIt0ieDZ9Turxb_YbNzj11wUbBbzjKfWh1BDlSx2Nw,183
|
|
11
|
+
pico_ioc/container.py,sha256=ipTpMwtCYuYfmJCjd7dfEtFFVR1PG0C1lZQp-wJ_UHw,21512
|
|
12
|
+
pico_ioc/decorators.py,sha256=ru_YeqyJ3gbfb6M8WeJZlBxfcBBEuGDvxpHJGzU6FIs,6412
|
|
13
|
+
pico_ioc/dependency_validator.py,sha256=BIR6pKntACiabF6CjNZ3m00RMnet9BPK1_9y1iCJ5KQ,4144
|
|
14
|
+
pico_ioc/event_bus.py,sha256=NSfmFPX6Zm2OmMJz16gJFYMhh65iI0n9UlC9M8GmO0c,8428
|
|
15
|
+
pico_ioc/exceptions.py,sha256=FBuajj5g29hAGODt2tAWuy2sG5mQojdSddaqFzim-aY,2383
|
|
16
|
+
pico_ioc/factory.py,sha256=oJXx_BYJuvV8oxYzs5I3gx9WM6uLYZ8GCc43gukNanc,1671
|
|
17
|
+
pico_ioc/locator.py,sha256=JD6psgdGGsBoCwov-G76BrmTfKUoJ22sdwa6wVdmQV8,5064
|
|
18
|
+
pico_ioc/provider_selector.py,sha256=pU7NbI5vifvUlJEjlRJmvveQUZVD47T24QmiP0CHRw0,1213
|
|
19
|
+
pico_ioc/registrar.py,sha256=0abgnJMJrEvEyqsvxBNTi6wl0iJHHjSaw75IuqGk56c,8531
|
|
20
|
+
pico_ioc/scope.py,sha256=TFchqFE9ooDCtYV_9YaLdeJDMtmLdNbB63nbZp-AqI8,6349
|
|
21
|
+
pico_ioc-2.2.0.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
|
|
22
|
+
pico_ioc-2.2.0.dist-info/METADATA,sha256=HnQ9xsjsyPFA7zCIkzkkHLSngVut4bZQUjHHBsk9PXI,12901
|
|
23
|
+
pico_ioc-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
pico_ioc-2.2.0.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
|
|
25
|
+
pico_ioc-2.2.0.dist-info/RECORD,,
|
pico_ioc-2.1.2.dist-info/RECORD
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
pico_ioc/__init__.py,sha256=i25Obx7aH_Oy5b6yjjnCswDgni7InIjrGEcG6vLAw6I,2414
|
|
2
|
-
pico_ioc/_version.py,sha256=m5qImnzcnIhayvILFVqEnXPYsN-vE0vxokygykKhRfw,22
|
|
3
|
-
pico_ioc/analysis.py,sha256=Iy3fuXCVLV8xtT-qp-uxsb1QptHBLLrLYbTSfDkQ-OA,4145
|
|
4
|
-
pico_ioc/aop.py,sha256=XcyzsuKPrVPk1_Jad7Mn-qwoL1y0ZuVWwRZBA-CslJk,13301
|
|
5
|
-
pico_ioc/api.py,sha256=0pcRFHzhDcX8ijd67xAsVrTejwXuJKz7kTKRUrIuX2s,6161
|
|
6
|
-
pico_ioc/component_scanner.py,sha256=S-9XNxrgyq_JFdc4Uqn2bEb-HxafSgIWylIurxyN_UA,7955
|
|
7
|
-
pico_ioc/config_builder.py,sha256=7kcYIq1Yrb46Tic7uLeaCDvLA-Sa_p1PIoGF00mivso,2848
|
|
8
|
-
pico_ioc/config_registrar.py,sha256=34iNQY1TUEPTXbb-QV1T-c5VKAn18hBcNt5MLhzDSfY,8456
|
|
9
|
-
pico_ioc/config_runtime.py,sha256=hiL1kCxhpjbfOdUaH71jMGNESDpWsaJkQXh7q1T71bg,12781
|
|
10
|
-
pico_ioc/constants.py,sha256=AhIt0ieDZ9Turxb_YbNzj11wUbBbzjKfWh1BDlSx2Nw,183
|
|
11
|
-
pico_ioc/container.py,sha256=Ys1yLjiB3Qxxm_fvWCEYLSeaJ18LseWmXueAW8kHunk,20874
|
|
12
|
-
pico_ioc/decorators.py,sha256=ru_YeqyJ3gbfb6M8WeJZlBxfcBBEuGDvxpHJGzU6FIs,6412
|
|
13
|
-
pico_ioc/dependency_validator.py,sha256=BIR6pKntACiabF6CjNZ3m00RMnet9BPK1_9y1iCJ5KQ,4144
|
|
14
|
-
pico_ioc/event_bus.py,sha256=nOL91JLYxap9kbb-HBGEhOVwtXN_bfI4q0mtSRZFlHk,8434
|
|
15
|
-
pico_ioc/exceptions.py,sha256=FBuajj5g29hAGODt2tAWuy2sG5mQojdSddaqFzim-aY,2383
|
|
16
|
-
pico_ioc/factory.py,sha256=oJXx_BYJuvV8oxYzs5I3gx9WM6uLYZ8GCc43gukNanc,1671
|
|
17
|
-
pico_ioc/locator.py,sha256=JD6psgdGGsBoCwov-G76BrmTfKUoJ22sdwa6wVdmQV8,5064
|
|
18
|
-
pico_ioc/provider_selector.py,sha256=pU7NbI5vifvUlJEjlRJmvveQUZVD47T24QmiP0CHRw0,1213
|
|
19
|
-
pico_ioc/registrar.py,sha256=hIk48nXghTdA3WBljCbw2q8J_6F_hCk1ljSi4Pb8P3A,8368
|
|
20
|
-
pico_ioc/scope.py,sha256=hOdTmjjfrRt8APXoS3lbTbSPxILi7flBXz_qpIkpoKw,6137
|
|
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
|