pico-ioc 2.1.1__py3-none-any.whl → 2.1.3__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 -5
- pico_ioc/aop.py +52 -17
- pico_ioc/config_runtime.py +5 -2
- pico_ioc/container.py +65 -12
- pico_ioc/event_bus.py +22 -19
- pico_ioc/locator.py +9 -1
- pico_ioc/registrar.py +2 -2
- pico_ioc/scope.py +21 -14
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.3.dist-info}/METADATA +128 -59
- pico_ioc-2.1.3.dist-info/RECORD +25 -0
- pico_ioc-2.1.1.dist-info/RECORD +0 -25
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.3.dist-info}/WHEEL +0 -0
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.3.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.1.1.dist-info → pico_ioc-2.1.3.dist-info}/top_level.txt +0 -0
pico_ioc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.1.
|
|
1
|
+
__version__ = '2.1.3'
|
pico_ioc/analysis.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
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
|
|
11
|
+
from .constants import LOGGER
|
|
5
12
|
|
|
6
13
|
KeyT = Union[str, type]
|
|
7
14
|
|
|
@@ -12,6 +19,8 @@ class DependencyRequest:
|
|
|
12
19
|
is_list: bool = False
|
|
13
20
|
qualifier: Optional[str] = None
|
|
14
21
|
is_optional: bool = False
|
|
22
|
+
is_dict: bool = False
|
|
23
|
+
dict_key_type: Any = None
|
|
15
24
|
|
|
16
25
|
def _extract_annotated(ann: Any) -> Tuple[Any, Optional[str]]:
|
|
17
26
|
qualifier = None
|
|
@@ -39,11 +48,27 @@ def _check_optional(ann: Any) -> Tuple[Any, bool]:
|
|
|
39
48
|
def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[DependencyRequest, ...]:
|
|
40
49
|
try:
|
|
41
50
|
sig = inspect.signature(callable_obj)
|
|
42
|
-
except (ValueError, TypeError):
|
|
51
|
+
except (ValueError, TypeError) as e:
|
|
52
|
+
LOGGER.debug(f"Could not analyze dependencies for {callable_obj!r}: {e}")
|
|
43
53
|
return ()
|
|
44
54
|
|
|
45
55
|
plan: List[DependencyRequest] = []
|
|
46
56
|
|
|
57
|
+
SUPPORTED_COLLECTION_ORIGINS = (
|
|
58
|
+
list,
|
|
59
|
+
set,
|
|
60
|
+
tuple,
|
|
61
|
+
frozenset,
|
|
62
|
+
collections.deque,
|
|
63
|
+
collections.abc.Iterable,
|
|
64
|
+
collections.abc.Collection,
|
|
65
|
+
collections.abc.Sequence,
|
|
66
|
+
collections.abc.MutableSequence,
|
|
67
|
+
collections.abc.MutableSet
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
SUPPORTED_DICT_ORIGINS = (dict, collections.abc.Mapping)
|
|
71
|
+
|
|
47
72
|
for name, param in sig.parameters.items():
|
|
48
73
|
if name in ("self", "cls"):
|
|
49
74
|
continue
|
|
@@ -56,19 +81,35 @@ def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[Dep
|
|
|
56
81
|
base_type, qualifier = _extract_annotated(base_type)
|
|
57
82
|
|
|
58
83
|
is_list = False
|
|
84
|
+
is_dict = False
|
|
59
85
|
elem_t = None
|
|
86
|
+
dict_key_t = None
|
|
60
87
|
|
|
61
88
|
origin = get_origin(base_type)
|
|
62
|
-
|
|
89
|
+
|
|
90
|
+
if origin in SUPPORTED_COLLECTION_ORIGINS:
|
|
63
91
|
is_list = True
|
|
64
92
|
elem_t = get_args(base_type)[0] if get_args(base_type) else Any
|
|
65
93
|
elem_t, list_qualifier = _extract_annotated(elem_t)
|
|
66
94
|
if qualifier is None:
|
|
67
95
|
qualifier = list_qualifier
|
|
96
|
+
elif origin in SUPPORTED_DICT_ORIGINS:
|
|
97
|
+
is_dict = True
|
|
98
|
+
args = get_args(base_type)
|
|
99
|
+
dict_key_t = args[0] if args else Any
|
|
100
|
+
elem_t = args[1] if len(args) > 1 else Any
|
|
101
|
+
elem_t, dict_qualifier = _extract_annotated(elem_t)
|
|
102
|
+
if qualifier is None:
|
|
103
|
+
qualifier = dict_qualifier
|
|
68
104
|
|
|
69
105
|
final_key: KeyT
|
|
106
|
+
final_dict_key_type: Any = None
|
|
107
|
+
|
|
70
108
|
if is_list:
|
|
71
109
|
final_key = elem_t if isinstance(elem_t, type) else Any
|
|
110
|
+
elif is_dict:
|
|
111
|
+
final_key = elem_t if isinstance(elem_t, type) else Any
|
|
112
|
+
final_dict_key_type = dict_key_t
|
|
72
113
|
elif isinstance(base_type, type):
|
|
73
114
|
final_key = base_type
|
|
74
115
|
elif isinstance(base_type, str):
|
|
@@ -84,9 +125,10 @@ def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[Dep
|
|
|
84
125
|
key=final_key,
|
|
85
126
|
is_list=is_list,
|
|
86
127
|
qualifier=qualifier,
|
|
87
|
-
is_optional=is_optional or (param.default is not inspect._empty)
|
|
128
|
+
is_optional=is_optional or (param.default is not inspect._empty),
|
|
129
|
+
is_dict=is_dict,
|
|
130
|
+
dict_key_type=final_dict_key_type
|
|
88
131
|
)
|
|
89
132
|
)
|
|
90
133
|
|
|
91
134
|
return tuple(plan)
|
|
92
|
-
|
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/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
|
@@ -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
|
|
@@ -204,7 +205,11 @@ class PicoContainer:
|
|
|
204
205
|
args = self._resolve_args(configure_deps)
|
|
205
206
|
res = m(**args)
|
|
206
207
|
if inspect.isawaitable(res):
|
|
207
|
-
|
|
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
|
+
)
|
|
208
213
|
return instance
|
|
209
214
|
|
|
210
215
|
async def runner():
|
|
@@ -251,10 +256,12 @@ class PicoContainer:
|
|
|
251
256
|
async def aget(self, key: KeyT) -> Any:
|
|
252
257
|
instance_or_awaitable, took_ms, was_cached = self._resolve_or_create_internal(key)
|
|
253
258
|
|
|
259
|
+
instance = instance_or_awaitable
|
|
254
260
|
if was_cached:
|
|
255
|
-
|
|
261
|
+
if isinstance(instance, UnifiedComponentProxy):
|
|
262
|
+
await instance._async_init_if_needed()
|
|
263
|
+
return instance
|
|
256
264
|
|
|
257
|
-
instance = instance_or_awaitable
|
|
258
265
|
if inspect.isawaitable(instance_or_awaitable):
|
|
259
266
|
instance = await instance_or_awaitable
|
|
260
267
|
|
|
@@ -268,6 +275,10 @@ class PicoContainer:
|
|
|
268
275
|
instance = instance_or_awaitable_configured
|
|
269
276
|
|
|
270
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
|
+
|
|
271
282
|
cache = self._cache_for(key)
|
|
272
283
|
cache.put(key, final_instance)
|
|
273
284
|
self.context.resolve_count += 1
|
|
@@ -281,7 +292,7 @@ class PicoContainer:
|
|
|
281
292
|
cls = type(instance)
|
|
282
293
|
for _, fn in inspect.getmembers(cls, predicate=lambda m: inspect.isfunction(m) or inspect.ismethod(m) or inspect.iscoroutinefunction(m)):
|
|
283
294
|
if getattr(fn, "_pico_interceptors_", None):
|
|
284
|
-
return UnifiedComponentProxy(container=self, target=instance)
|
|
295
|
+
return UnifiedComponentProxy(container=self, target=instance, component_key=key)
|
|
285
296
|
return instance
|
|
286
297
|
|
|
287
298
|
def _iterate_cleanup_targets(self) -> Iterable[Any]:
|
|
@@ -373,7 +384,7 @@ class PicoContainer:
|
|
|
373
384
|
self.cleanup_all()
|
|
374
385
|
PicoContainer._container_registry.pop(self.container_id, None)
|
|
375
386
|
|
|
376
|
-
def build_resolution_graph(self)
|
|
387
|
+
def build_resolution_graph(self):
|
|
377
388
|
return _build_resolution_graph(self._locator)
|
|
378
389
|
|
|
379
390
|
def export_graph(
|
|
@@ -423,7 +434,7 @@ class PicoContainer:
|
|
|
423
434
|
pid = _node_id(parent)
|
|
424
435
|
for child in deps:
|
|
425
436
|
cid = _node_id(child)
|
|
426
|
-
lines.append(f" {pid} -> {
|
|
437
|
+
lines.append(f" {pid} -> {child};")
|
|
427
438
|
|
|
428
439
|
lines.append("}")
|
|
429
440
|
|
|
@@ -433,19 +444,61 @@ class PicoContainer:
|
|
|
433
444
|
|
|
434
445
|
def _resolve_args(self, dependencies: Tuple[DependencyRequest, ...]) -> Dict[str, Any]:
|
|
435
446
|
kwargs: Dict[str, Any] = {}
|
|
436
|
-
if not dependencies:
|
|
447
|
+
if not dependencies or self._locator is None:
|
|
437
448
|
return kwargs
|
|
438
449
|
|
|
439
450
|
for dep in dependencies:
|
|
440
451
|
if dep.is_list:
|
|
441
452
|
keys: Tuple[KeyT, ...] = ()
|
|
442
|
-
if
|
|
453
|
+
if isinstance(dep.key, type):
|
|
443
454
|
keys = tuple(self._locator.collect_by_type(dep.key, dep.qualifier))
|
|
444
455
|
kwargs[dep.parameter_name] = [self.get(k) for k in keys]
|
|
445
456
|
continue
|
|
457
|
+
|
|
458
|
+
if dep.is_dict:
|
|
459
|
+
value_type = dep.key
|
|
460
|
+
key_type = dep.dict_key_type
|
|
461
|
+
result_map: Dict[Any, Any] = {}
|
|
462
|
+
|
|
463
|
+
keys_to_resolve: Tuple[KeyT, ...] = ()
|
|
464
|
+
if isinstance(value_type, type):
|
|
465
|
+
keys_to_resolve = tuple(self._locator.collect_by_type(value_type, dep.qualifier))
|
|
466
|
+
|
|
467
|
+
for comp_key in keys_to_resolve:
|
|
468
|
+
instance = self.get(comp_key)
|
|
469
|
+
md = self._locator._metadata.get(comp_key)
|
|
470
|
+
if md is None:
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
dict_key: Any = None
|
|
474
|
+
if key_type is str:
|
|
475
|
+
dict_key = md.pico_name
|
|
476
|
+
if dict_key is None:
|
|
477
|
+
if isinstance(comp_key, str):
|
|
478
|
+
dict_key = comp_key
|
|
479
|
+
else:
|
|
480
|
+
dict_key = getattr(comp_key, "__name__", str(comp_key))
|
|
481
|
+
elif key_type is type or key_type is Type:
|
|
482
|
+
dict_key = md.concrete_class or md.provided_type
|
|
483
|
+
elif key_type is Any:
|
|
484
|
+
dict_key = md.pico_name
|
|
485
|
+
if dict_key is None:
|
|
486
|
+
if isinstance(comp_key, str):
|
|
487
|
+
dict_key = comp_key
|
|
488
|
+
else:
|
|
489
|
+
dict_key = getattr(comp_key, "__name__", str(comp_key))
|
|
490
|
+
|
|
491
|
+
if dict_key is not None:
|
|
492
|
+
if (key_type is type or key_type is Type) and not isinstance(dict_key, type):
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
result_map[dict_key] = instance
|
|
496
|
+
|
|
497
|
+
kwargs[dep.parameter_name] = result_map
|
|
498
|
+
continue
|
|
446
499
|
|
|
447
500
|
primary_key = dep.key
|
|
448
|
-
if isinstance(primary_key, str)
|
|
501
|
+
if isinstance(primary_key, str):
|
|
449
502
|
mapped = self._locator.find_key_by_name(primary_key)
|
|
450
503
|
primary_key = mapped if mapped is not None else primary_key
|
|
451
504
|
|
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/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)
|
pico_ioc/registrar.py
CHANGED
|
@@ -61,8 +61,8 @@ class Registrar:
|
|
|
61
61
|
for key, md in list(self._metadata.items()):
|
|
62
62
|
if md.lazy:
|
|
63
63
|
original = self._factory.get(key, origin='lazy')
|
|
64
|
-
def lazy_proxy_provider(_orig=original, _p=pico):
|
|
65
|
-
return UnifiedComponentProxy(container=_p, object_creator=_orig)
|
|
64
|
+
def lazy_proxy_provider(_orig=original, _p=pico, _k=key):
|
|
65
|
+
return UnifiedComponentProxy(container=_p, object_creator=_orig, component_key=_k)
|
|
66
66
|
self._factory.bind(key, lazy_proxy_provider)
|
|
67
67
|
|
|
68
68
|
|
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
|
|
@@ -95,9 +94,10 @@ class ScopeManager:
|
|
|
95
94
|
class ScopedCaches:
|
|
96
95
|
def __init__(self, max_scopes_per_type: int = 2048) -> None:
|
|
97
96
|
self._singleton = ComponentContainer()
|
|
98
|
-
self._by_scope: Dict[str,
|
|
97
|
+
self._by_scope: Dict[str, Dict[Any, ComponentContainer]] = {}
|
|
99
98
|
self._max = int(max_scopes_per_type)
|
|
100
99
|
self._no_cache = _NoCacheContainer()
|
|
100
|
+
|
|
101
101
|
def _cleanup_object(self, obj: Any) -> None:
|
|
102
102
|
try:
|
|
103
103
|
from .constants import PICO_META
|
|
@@ -132,15 +132,19 @@ class ScopedCaches:
|
|
|
132
132
|
return self._singleton
|
|
133
133
|
if scope == "prototype":
|
|
134
134
|
return self._no_cache
|
|
135
|
+
|
|
135
136
|
sid = scopes.get_id(scope)
|
|
136
|
-
|
|
137
|
+
|
|
138
|
+
if sid is None:
|
|
139
|
+
raise ScopeError(
|
|
140
|
+
f"Cannot resolve component in scope '{scope}': No active scope ID found. "
|
|
141
|
+
f"Are you trying to use a {scope}-scoped component outside of its context?"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
bucket = self._by_scope.setdefault(scope, {})
|
|
137
145
|
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)
|
|
146
|
+
return bucket[sid]
|
|
147
|
+
|
|
144
148
|
c = ComponentContainer()
|
|
145
149
|
bucket[sid] = c
|
|
146
150
|
return c
|
|
@@ -159,8 +163,11 @@ class ScopedCaches:
|
|
|
159
163
|
bucket = self._by_scope.get(scope)
|
|
160
164
|
if not bucket:
|
|
161
165
|
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
|
|
167
|
+
# Manual cleanup if needed, though we rely on explicit cleanup now
|
|
168
|
+
if len(bucket) > keep:
|
|
169
|
+
# Simple eviction strategy if forced manually
|
|
170
|
+
keys_to_remove = list(bucket.keys())[:len(bucket)-keep]
|
|
171
|
+
for k in keys_to_remove:
|
|
172
|
+
container = bucket.pop(k)
|
|
173
|
+
self._cleanup_container(container)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pico-ioc
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
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,24 @@ As Python systems evolve, wiring dependencies by hand becomes fragile and unmain
|
|
|
108
112
|
|
|
109
113
|
```bash
|
|
110
114
|
pip install pico-ioc
|
|
111
|
-
|
|
115
|
+
```
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
Optional extras:
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
- YAML configuration support (requires PyYAML)
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
```bash
|
|
122
|
+
pip install pico-ioc[yaml]
|
|
123
|
+
```
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
-----
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
### ⚠️ Important Note for v2.1.3+
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# pip install pico-ioc[graphviz] # Consider removing if not used by code
|
|
130
|
-
```
|
|
129
|
+
**Breaking Behavior in Custom Integrations:**
|
|
130
|
+
As of version 2.1.3, **Scope LRU Eviction has been removed** to guarantee data integrity under high load.
|
|
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
133
|
|
|
132
134
|
-----
|
|
133
135
|
|
|
@@ -139,7 +141,7 @@ from dataclasses import dataclass
|
|
|
139
141
|
from pico_ioc import component, configured, configuration, init, EnvSource
|
|
140
142
|
|
|
141
143
|
# 1. Define configuration with @configured
|
|
142
|
-
@configured(prefix="APP_", mapping="auto")
|
|
144
|
+
@configured(prefix="APP_", mapping="auto") # Auto-detects flat mapping
|
|
143
145
|
@dataclass
|
|
144
146
|
class Config:
|
|
145
147
|
db_url: str = "sqlite:///demo.db"
|
|
@@ -147,14 +149,14 @@ class Config:
|
|
|
147
149
|
# 2. Define components
|
|
148
150
|
@component
|
|
149
151
|
class Repo:
|
|
150
|
-
def __init__(self, cfg: Config):
|
|
152
|
+
def __init__(self, cfg: Config): # Inject config
|
|
151
153
|
self.cfg = cfg
|
|
152
154
|
def fetch(self):
|
|
153
155
|
return f"fetching from {self.cfg.db_url}"
|
|
154
156
|
|
|
155
157
|
@component
|
|
156
158
|
class Service:
|
|
157
|
-
def __init__(self, repo: Repo):
|
|
159
|
+
def __init__(self, repo: Repo): # Inject Repo
|
|
158
160
|
self.repo = repo
|
|
159
161
|
def run(self):
|
|
160
162
|
return self.repo.fetch()
|
|
@@ -164,11 +166,11 @@ os.environ['APP_DB_URL'] = 'postgresql://user:pass@host/db'
|
|
|
164
166
|
|
|
165
167
|
# 3. Build configuration context
|
|
166
168
|
config_ctx = configuration(
|
|
167
|
-
EnvSource(prefix="")
|
|
169
|
+
EnvSource(prefix="") # Read APP_DB_URL from environment
|
|
168
170
|
)
|
|
169
171
|
|
|
170
172
|
# 4. Initialize container
|
|
171
|
-
container = init(modules=[__name__], config=config_ctx)
|
|
173
|
+
container = init(modules=[__name__], config=config_ctx) # Pass context via 'config'
|
|
172
174
|
|
|
173
175
|
# 5. Get and use the service
|
|
174
176
|
svc = container.get(Service)
|
|
@@ -178,7 +180,7 @@ print(svc.run())
|
|
|
178
180
|
del os.environ['APP_DB_URL']
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
Output:
|
|
182
184
|
|
|
183
185
|
```
|
|
184
186
|
fetching from postgresql://user:pass@host/db
|
|
@@ -199,7 +201,7 @@ test_config_ctx = configuration()
|
|
|
199
201
|
container = init(
|
|
200
202
|
modules=[__name__],
|
|
201
203
|
config=test_config_ctx,
|
|
202
|
-
overrides={Repo: FakeRepo()}
|
|
204
|
+
overrides={Repo: FakeRepo()} # Replace Repo with FakeRepo
|
|
203
205
|
)
|
|
204
206
|
|
|
205
207
|
svc = container.get(Service)
|
|
@@ -208,10 +210,56 @@ assert svc.run() == "fake-data"
|
|
|
208
210
|
|
|
209
211
|
-----
|
|
210
212
|
|
|
213
|
+
## 🧰 Profiles
|
|
214
|
+
|
|
215
|
+
Use profiles to enable/disable components or configuration branches conditionally.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Enable "test" profile when bootstrapping the container
|
|
219
|
+
container = init(
|
|
220
|
+
modules=[__name__],
|
|
221
|
+
profiles=["test"]
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Profiles are typically referenced in decorators or configuration mappings to include/exclude components and bindings.
|
|
226
|
+
|
|
227
|
+
-----
|
|
228
|
+
|
|
229
|
+
## ⚡ Async Components
|
|
230
|
+
|
|
231
|
+
Pico-IoC supports async lifecycle and resolution.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
import asyncio
|
|
235
|
+
from pico_ioc import component, init
|
|
236
|
+
|
|
237
|
+
@component
|
|
238
|
+
class AsyncRepo:
|
|
239
|
+
async def __ainit__(self):
|
|
240
|
+
# e.g., open async connections
|
|
241
|
+
self.ready = True
|
|
242
|
+
|
|
243
|
+
async def fetch(self):
|
|
244
|
+
return "async-data"
|
|
245
|
+
|
|
246
|
+
async def main():
|
|
247
|
+
container = init(modules=[__name__])
|
|
248
|
+
repo = await container.aget(AsyncRepo) # Async resolution
|
|
249
|
+
print(await repo.fetch())
|
|
250
|
+
|
|
251
|
+
asyncio.run(main())
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- `__ainit__` runs after construction if defined.
|
|
255
|
+
- Use `container.aget(Type)` to resolve components that require async initialization or whose providers are async.
|
|
256
|
+
|
|
257
|
+
-----
|
|
258
|
+
|
|
211
259
|
## 🩺 Lifecycle & AOP
|
|
212
260
|
|
|
213
261
|
```python
|
|
214
|
-
import time
|
|
262
|
+
import time
|
|
215
263
|
from pico_ioc import component, init, intercepted_by, MethodInterceptor, MethodCtx
|
|
216
264
|
|
|
217
265
|
# Define an interceptor component
|
|
@@ -232,7 +280,7 @@ class LogInterceptor(MethodInterceptor):
|
|
|
232
280
|
|
|
233
281
|
@component
|
|
234
282
|
class Demo:
|
|
235
|
-
@intercepted_by(LogInterceptor)
|
|
283
|
+
@intercepted_by(LogInterceptor) # Apply the interceptor
|
|
236
284
|
def work(self):
|
|
237
285
|
print(" Working...")
|
|
238
286
|
time.sleep(0.01)
|
|
@@ -244,7 +292,7 @@ result = c.get(Demo).work()
|
|
|
244
292
|
print(f"Result: {result}")
|
|
245
293
|
```
|
|
246
294
|
|
|
247
|
-
|
|
295
|
+
Output:
|
|
248
296
|
|
|
249
297
|
```
|
|
250
298
|
→ calling Demo.work
|
|
@@ -255,19 +303,41 @@ Result: ok
|
|
|
255
303
|
|
|
256
304
|
-----
|
|
257
305
|
|
|
306
|
+
## 👁️ Observability & Cleanup
|
|
307
|
+
|
|
308
|
+
- Export a dependency graph in DOT format:
|
|
309
|
+
|
|
310
|
+
```python
|
|
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
|
+
```
|
|
316
|
+
|
|
317
|
+
- Health checks:
|
|
318
|
+
- Annotate health probes inside components with `@health` for container-level reporting.
|
|
319
|
+
- The container exposes health information that can be queried in observability tooling.
|
|
320
|
+
|
|
321
|
+
- Container cleanup:
|
|
322
|
+
- For sync components: `container.close()`
|
|
323
|
+
- For async components/resources: `await container.aclose()`
|
|
324
|
+
|
|
325
|
+
Use cleanup in application shutdown hooks to release resources deterministically.
|
|
326
|
+
|
|
327
|
+
-----
|
|
328
|
+
|
|
258
329
|
## 📖 Documentation
|
|
259
330
|
|
|
260
331
|
The full documentation is available within the `docs/` directory of the project repository. Start with `docs/README.md` for navigation.
|
|
261
332
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* **ADR Index:** `docs/adr/README.md`
|
|
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`
|
|
271
341
|
|
|
272
342
|
-----
|
|
273
343
|
|
|
@@ -282,11 +352,10 @@ tox
|
|
|
282
352
|
|
|
283
353
|
## 🧾 Changelog
|
|
284
354
|
|
|
285
|
-
See [CHANGELOG.md](./CHANGELOG.md) —
|
|
355
|
+
See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.0+.
|
|
286
356
|
|
|
287
357
|
-----
|
|
288
358
|
|
|
289
359
|
## 📜 License
|
|
290
360
|
|
|
291
361
|
MIT — [LICENSE](https://opensource.org/licenses/MIT)
|
|
292
|
-
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
pico_ioc/__init__.py,sha256=i25Obx7aH_Oy5b6yjjnCswDgni7InIjrGEcG6vLAw6I,2414
|
|
2
|
+
pico_ioc/_version.py,sha256=hgD1miBO_f3fboq1GKyV4DdK_igCLGJFnZRD7l9oNRs,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=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=qdPIbGMXOf6KWMM7cva6W-hhbhybEY0swZN01R1emCg,12756
|
|
10
|
+
pico_ioc/constants.py,sha256=AhIt0ieDZ9Turxb_YbNzj11wUbBbzjKfWh1BDlSx2Nw,183
|
|
11
|
+
pico_ioc/container.py,sha256=SEEJCUHIOUz4Dghdmac2rDrv65Zlm2wiBKZt4WbZoDc,21354
|
|
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=O22FYrMXQ4TvBf25b0CB1wwu9tjL-5e00n9Ubd8suNs,8394
|
|
20
|
+
pico_ioc/scope.py,sha256=JmFJPSb9uG2oRSZ7YreCozOgqDHfYsYLcyCpqVI4luU,6427
|
|
21
|
+
pico_ioc-2.1.3.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
|
|
22
|
+
pico_ioc-2.1.3.dist-info/METADATA,sha256=-PWHmvXzShw_t9D-_vbGBN24aG7afVchr3ZFyGR0TrQ,12910
|
|
23
|
+
pico_ioc-2.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
pico_ioc-2.1.3.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
|
|
25
|
+
pico_ioc-2.1.3.dist-info/RECORD,,
|
pico_ioc-2.1.1.dist-info/RECORD
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
pico_ioc/__init__.py,sha256=i25Obx7aH_Oy5b6yjjnCswDgni7InIjrGEcG6vLAw6I,2414
|
|
2
|
-
pico_ioc/_version.py,sha256=Aht2295j8FswZ-nPYofCYr3fBZ6Uyf0thTfl5Oc2mWA,22
|
|
3
|
-
pico_ioc/analysis.py,sha256=k49R-HcDyvpSNid8mxv7Fc6fPHnDu1C_b4HxrGLNF2g,2780
|
|
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=gXgQT12ChpexbZALUfb0YYohlcRbUUeJ8-ltdR7xitc,18956
|
|
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=4WN1qvXXW_LRInB2XJR8pTgIuJ8RyWBSpVo28HwtlL0,4737
|
|
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.1.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
|
|
22
|
-
pico_ioc-2.1.1.dist-info/METADATA,sha256=deaBUX6MOzClrMVDDhk5siAh2W0QrtQI-w4n2ZI-gGI,10673
|
|
23
|
-
pico_ioc-2.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
-
pico_ioc-2.1.1.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
|
|
25
|
-
pico_ioc-2.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|