pico-ioc 1.5.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pico_ioc/__init__.py +82 -57
- pico_ioc/_version.py +1 -1
- pico_ioc/aop.py +281 -0
- pico_ioc/api.py +1137 -198
- pico_ioc/config_runtime.py +289 -0
- pico_ioc/constants.py +10 -0
- pico_ioc/container.py +420 -148
- pico_ioc/event_bus.py +223 -0
- pico_ioc/exceptions.py +72 -0
- pico_ioc/factory.py +48 -0
- pico_ioc/locator.py +53 -0
- pico_ioc/scope.py +155 -39
- pico_ioc-2.0.1.dist-info/METADATA +243 -0
- pico_ioc-2.0.1.dist-info/RECORD +17 -0
- pico_ioc/_state.py +0 -75
- pico_ioc/builder.py +0 -210
- pico_ioc/config.py +0 -332
- pico_ioc/decorators.py +0 -120
- pico_ioc/infra.py +0 -196
- pico_ioc/interceptors.py +0 -76
- pico_ioc/plugins.py +0 -28
- pico_ioc/policy.py +0 -245
- pico_ioc/proxy.py +0 -115
- pico_ioc/public_api.py +0 -76
- pico_ioc/resolver.py +0 -101
- pico_ioc/scanner.py +0 -178
- pico_ioc/utils.py +0 -25
- pico_ioc-1.5.0.dist-info/METADATA +0 -249
- pico_ioc-1.5.0.dist-info/RECORD +0 -23
- {pico_ioc-1.5.0.dist-info → pico_ioc-2.0.1.dist-info}/WHEEL +0 -0
- {pico_ioc-1.5.0.dist-info → pico_ioc-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-1.5.0.dist-info → pico_ioc-2.0.1.dist-info}/top_level.txt +0 -0
pico_ioc/api.py
CHANGED
|
@@ -1,222 +1,1161 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import inspect
|
|
4
|
+
import functools
|
|
4
5
|
import importlib
|
|
6
|
+
import pkgutil
|
|
5
7
|
import logging
|
|
6
|
-
from
|
|
7
|
-
from typing import Callable, Optional, Tuple,
|
|
8
|
-
|
|
8
|
+
from dataclasses import is_dataclass, fields, dataclass, MISSING
|
|
9
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union, get_args, get_origin, Annotated, Protocol
|
|
10
|
+
from .constants import LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
ProviderNotFoundError,
|
|
13
|
+
CircularDependencyError,
|
|
14
|
+
ScopeError,
|
|
15
|
+
ConfigurationError,
|
|
16
|
+
SerializationError,
|
|
17
|
+
InvalidBindingError,
|
|
18
|
+
)
|
|
19
|
+
from .factory import ComponentFactory, ProviderMetadata, DeferredProvider
|
|
20
|
+
from .locator import ComponentLocator
|
|
21
|
+
from .scope import ScopeManager, ScopedCaches
|
|
9
22
|
from .container import PicoContainer
|
|
10
|
-
from .
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
from .aop import UnifiedComponentProxy
|
|
24
|
+
|
|
25
|
+
KeyT = Union[str, type]
|
|
26
|
+
Provider = Callable[[], Any]
|
|
27
|
+
|
|
28
|
+
class ConfigSource(Protocol):
|
|
29
|
+
def get(self, key: str) -> Optional[str]: ...
|
|
30
|
+
|
|
31
|
+
class EnvSource:
|
|
32
|
+
def __init__(self, prefix: str = "") -> None:
|
|
33
|
+
self.prefix = prefix
|
|
34
|
+
def get(self, key: str) -> Optional[str]:
|
|
35
|
+
return os.environ.get(self.prefix + key)
|
|
36
|
+
|
|
37
|
+
class FileSource:
|
|
38
|
+
def __init__(self, path: str, prefix: str = "") -> None:
|
|
39
|
+
self.prefix = prefix
|
|
40
|
+
try:
|
|
41
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
42
|
+
self._data = json.load(f)
|
|
43
|
+
except Exception:
|
|
44
|
+
self._data = {}
|
|
45
|
+
def get(self, key: str) -> Optional[str]:
|
|
46
|
+
k = self.prefix + key
|
|
47
|
+
v = self._data
|
|
48
|
+
for part in k.split("__"):
|
|
49
|
+
if isinstance(v, dict) and part in v:
|
|
50
|
+
v = v[part]
|
|
51
|
+
else:
|
|
52
|
+
return None
|
|
53
|
+
if isinstance(v, (str, int, float, bool)):
|
|
54
|
+
return str(v)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def _meta_get(obj: Any) -> Dict[str, Any]:
|
|
58
|
+
m = getattr(obj, PICO_META, None)
|
|
59
|
+
if m is None:
|
|
60
|
+
m = {}
|
|
61
|
+
setattr(obj, PICO_META, m)
|
|
62
|
+
return m
|
|
63
|
+
|
|
64
|
+
def component(
|
|
65
|
+
cls=None,
|
|
66
|
+
*,
|
|
67
|
+
name: Any = None,
|
|
68
|
+
qualifiers: Iterable[str] = (),
|
|
69
|
+
scope: str = "singleton",
|
|
70
|
+
primary: bool = False,
|
|
71
|
+
lazy: bool = False,
|
|
72
|
+
conditional_profiles: Iterable[str] = (),
|
|
73
|
+
conditional_require_env: Iterable[str] = (),
|
|
74
|
+
conditional_predicate: Optional[Callable[[], bool]] = None,
|
|
75
|
+
on_missing_selector: Optional[object] = None,
|
|
76
|
+
on_missing_priority: int = 0,
|
|
77
|
+
):
|
|
78
|
+
def dec(c):
|
|
79
|
+
setattr(c, PICO_INFRA, "component")
|
|
80
|
+
setattr(c, PICO_NAME, name if name is not None else getattr(c, "__name__", str(c)))
|
|
81
|
+
setattr(c, PICO_KEY, name if name is not None else c)
|
|
82
|
+
m = _meta_get(c)
|
|
83
|
+
m["qualifier"] = tuple(str(q) for q in qualifiers or ())
|
|
84
|
+
m["scope"] = scope
|
|
85
|
+
if primary:
|
|
86
|
+
m["primary"] = True
|
|
87
|
+
if lazy:
|
|
88
|
+
m["lazy"] = True
|
|
89
|
+
if conditional_profiles or conditional_require_env or conditional_predicate is not None:
|
|
90
|
+
m["conditional"] = {
|
|
91
|
+
"profiles": tuple(p for p in conditional_profiles or ()),
|
|
92
|
+
"require_env": tuple(e for e in conditional_require_env or ()),
|
|
93
|
+
"predicate": conditional_predicate,
|
|
94
|
+
}
|
|
95
|
+
if on_missing_selector is not None:
|
|
96
|
+
m["on_missing"] = {"selector": on_missing_selector, "priority": int(on_missing_priority)}
|
|
97
|
+
return c
|
|
98
|
+
return dec(cls) if cls else dec
|
|
99
|
+
|
|
100
|
+
def factory(
|
|
101
|
+
cls=None,
|
|
102
|
+
*,
|
|
103
|
+
name: Any = None,
|
|
104
|
+
qualifiers: Iterable[str] = (),
|
|
105
|
+
scope: str = "singleton",
|
|
106
|
+
primary: bool = False,
|
|
107
|
+
lazy: bool = False,
|
|
108
|
+
conditional_profiles: Iterable[str] = (),
|
|
109
|
+
conditional_require_env: Iterable[str] = (),
|
|
110
|
+
conditional_predicate: Optional[Callable[[], bool]] = None,
|
|
111
|
+
on_missing_selector: Optional[object] = None,
|
|
112
|
+
on_missing_priority: int = 0,
|
|
113
|
+
):
|
|
114
|
+
def dec(c):
|
|
115
|
+
setattr(c, PICO_INFRA, "factory")
|
|
116
|
+
setattr(c, PICO_NAME, name if name is not None else getattr(c, "__name__", str(c)))
|
|
117
|
+
m = _meta_get(c)
|
|
118
|
+
m["qualifier"] = tuple(str(q) for q in qualifiers or ())
|
|
119
|
+
m["scope"] = scope
|
|
120
|
+
if primary:
|
|
121
|
+
m["primary"] = True
|
|
122
|
+
if lazy:
|
|
123
|
+
m["lazy"] = True
|
|
124
|
+
if conditional_profiles or conditional_require_env or conditional_predicate is not None:
|
|
125
|
+
m["conditional"] = {
|
|
126
|
+
"profiles": tuple(p for p in conditional_profiles or ()),
|
|
127
|
+
"require_env": tuple(e for e in conditional_require_env or ()),
|
|
128
|
+
"predicate": conditional_predicate,
|
|
129
|
+
}
|
|
130
|
+
if on_missing_selector is not None:
|
|
131
|
+
m["on_missing"] = {"selector": on_missing_selector, "priority": int(on_missing_priority)}
|
|
132
|
+
return c
|
|
133
|
+
return dec(cls) if cls else dec
|
|
134
|
+
|
|
135
|
+
def provides(
|
|
136
|
+
key: Any,
|
|
137
|
+
*,
|
|
138
|
+
name: Any = None,
|
|
139
|
+
qualifiers: Iterable[str] = (),
|
|
140
|
+
scope: str = "singleton",
|
|
141
|
+
primary: bool = False,
|
|
142
|
+
lazy: bool = False,
|
|
143
|
+
conditional_profiles: Iterable[str] = (),
|
|
144
|
+
conditional_require_env: Iterable[str] = (),
|
|
145
|
+
conditional_predicate: Optional[Callable[[], bool]] = None,
|
|
146
|
+
on_missing_selector: Optional[object] = None,
|
|
147
|
+
on_missing_priority: int = 0,
|
|
148
|
+
):
|
|
149
|
+
def dec(fn):
|
|
150
|
+
target = fn.__func__ if isinstance(fn, (staticmethod, classmethod)) else fn
|
|
151
|
+
@functools.wraps(target)
|
|
152
|
+
def w(*a, **k):
|
|
153
|
+
return target(*a, **k)
|
|
154
|
+
setattr(w, PICO_INFRA, "provides")
|
|
155
|
+
setattr(w, PICO_NAME, name if name is not None else key)
|
|
156
|
+
setattr(w, PICO_KEY, key)
|
|
157
|
+
m = _meta_get(w)
|
|
158
|
+
m["qualifier"] = tuple(str(q) for q in qualifiers or ())
|
|
159
|
+
m["scope"] = scope
|
|
160
|
+
if primary:
|
|
161
|
+
m["primary"] = True
|
|
162
|
+
if lazy:
|
|
163
|
+
m["lazy"] = True
|
|
164
|
+
if conditional_profiles or conditional_require_env or conditional_predicate is not None:
|
|
165
|
+
m["conditional"] = {
|
|
166
|
+
"profiles": tuple(p for p in conditional_profiles or ()),
|
|
167
|
+
"require_env": tuple(e for e in conditional_require_env or ()),
|
|
168
|
+
"predicate": conditional_predicate,
|
|
169
|
+
}
|
|
170
|
+
if on_missing_selector is not None:
|
|
171
|
+
m["on_missing"] = {"selector": on_missing_selector, "priority": int(on_missing_priority)}
|
|
172
|
+
return w
|
|
173
|
+
return dec
|
|
174
|
+
|
|
175
|
+
class Qualifier(str):
|
|
176
|
+
__slots__ = ()
|
|
177
|
+
|
|
178
|
+
def configuration(cls=None, *, prefix: Optional[str] = None):
|
|
179
|
+
def dec(c):
|
|
180
|
+
setattr(c, PICO_INFRA, "configuration")
|
|
181
|
+
m = _meta_get(c)
|
|
182
|
+
if prefix is not None:
|
|
183
|
+
m["config_prefix"] = prefix
|
|
184
|
+
return c
|
|
185
|
+
return dec(cls) if cls else dec
|
|
186
|
+
|
|
187
|
+
def configure(fn):
|
|
188
|
+
m = _meta_get(fn)
|
|
189
|
+
m["configure"] = True
|
|
190
|
+
return fn
|
|
191
|
+
|
|
192
|
+
def cleanup(fn):
|
|
193
|
+
m = _meta_get(fn)
|
|
194
|
+
m["cleanup"] = True
|
|
195
|
+
return fn
|
|
15
196
|
|
|
197
|
+
def configured(target: Any, *, prefix: Optional[str] = None):
|
|
198
|
+
def dec(cls):
|
|
199
|
+
setattr(cls, PICO_INFRA, "configured")
|
|
200
|
+
m = _meta_get(cls)
|
|
201
|
+
m["configured"] = {"target": target, "prefix": prefix}
|
|
202
|
+
return cls
|
|
203
|
+
return dec
|
|
16
204
|
|
|
17
|
-
def
|
|
18
|
-
|
|
205
|
+
def _truthy(s: str) -> bool:
|
|
206
|
+
return s.strip().lower() in {"1", "true", "yes", "on", "y", "t"}
|
|
19
207
|
|
|
20
|
-
def
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
if
|
|
24
|
-
|
|
208
|
+
def _coerce(val: Optional[str], t: type) -> Any:
|
|
209
|
+
if val is None:
|
|
210
|
+
return None
|
|
211
|
+
if t is str:
|
|
212
|
+
return val
|
|
213
|
+
if t is int:
|
|
214
|
+
return int(val)
|
|
215
|
+
if t is float:
|
|
216
|
+
return float(val)
|
|
217
|
+
if t is bool:
|
|
218
|
+
return _truthy(val)
|
|
219
|
+
org = get_origin(t)
|
|
220
|
+
if org is Union:
|
|
221
|
+
args = [a for a in get_args(t) if a is not type(None)]
|
|
222
|
+
if not args:
|
|
223
|
+
return None
|
|
224
|
+
return _coerce(val, args[0])
|
|
225
|
+
return val
|
|
25
226
|
|
|
26
|
-
|
|
27
|
-
|
|
227
|
+
def _upper_key(name: str) -> str:
|
|
228
|
+
return name.upper()
|
|
229
|
+
|
|
230
|
+
def _lookup(sources: Tuple[ConfigSource, ...], key: str) -> Optional[str]:
|
|
231
|
+
for src in sources:
|
|
232
|
+
v = src.get(key)
|
|
233
|
+
if v is not None:
|
|
234
|
+
return v
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def _build_settings_instance(cls: type, sources: Tuple[ConfigSource, ...], prefix: Optional[str]) -> Any:
|
|
238
|
+
if not is_dataclass(cls):
|
|
239
|
+
raise ConfigurationError(f"Configuration class {getattr(cls, '__name__', str(cls))} must be a dataclass")
|
|
240
|
+
values: Dict[str, Any] = {}
|
|
241
|
+
for f in fields(cls):
|
|
242
|
+
base_key = _upper_key(f.name)
|
|
243
|
+
keys_to_try = []
|
|
244
|
+
if prefix:
|
|
245
|
+
keys_to_try.append(prefix + base_key)
|
|
246
|
+
keys_to_try.append(base_key)
|
|
247
|
+
raw = None
|
|
248
|
+
for k in keys_to_try:
|
|
249
|
+
raw = _lookup(sources, k)
|
|
250
|
+
if raw is not None:
|
|
251
|
+
break
|
|
252
|
+
if raw is None:
|
|
253
|
+
if f.default is not MISSING or f.default_factory is not MISSING:
|
|
254
|
+
continue
|
|
255
|
+
raise ConfigurationError(f"Missing configuration key: {(prefix or '') + base_key}")
|
|
256
|
+
values[f.name] = _coerce(raw, f.type if isinstance(f.type, type) or get_origin(f.type) else str)
|
|
257
|
+
return cls(**values)
|
|
258
|
+
|
|
259
|
+
def _extract_list_req(ann: Any):
|
|
260
|
+
def read_qualifier(metas: Iterable[Any]):
|
|
261
|
+
for m in metas:
|
|
262
|
+
if isinstance(m, Qualifier):
|
|
263
|
+
return str(m)
|
|
264
|
+
return None
|
|
265
|
+
origin = get_origin(ann)
|
|
266
|
+
if origin is Annotated:
|
|
267
|
+
args = get_args(ann)
|
|
268
|
+
base = args[0] if args else Any
|
|
269
|
+
metas = args[1:] if len(args) > 1 else ()
|
|
270
|
+
is_list, elem_t, qual = _extract_list_req(base)
|
|
271
|
+
if qual is None:
|
|
272
|
+
qual = read_qualifier(metas)
|
|
273
|
+
return is_list, elem_t, qual
|
|
274
|
+
if origin in (list, List):
|
|
275
|
+
elem = get_args(ann)[0] if get_args(ann) else Any
|
|
276
|
+
if get_origin(elem) is Annotated:
|
|
277
|
+
eargs = get_args(elem)
|
|
278
|
+
ebase = eargs[0] if eargs else Any
|
|
279
|
+
emetas = eargs[1:] if len(eargs) > 1 else ()
|
|
280
|
+
qual = read_qualifier(emetas)
|
|
281
|
+
return True, ebase if isinstance(ebase, type) else Any, qual
|
|
282
|
+
return True, elem if isinstance(elem, type) else Any, None
|
|
283
|
+
return False, None, None
|
|
284
|
+
|
|
285
|
+
def _implements_protocol(typ: type, proto: type) -> bool:
|
|
286
|
+
if not getattr(proto, "_is_protocol", False):
|
|
287
|
+
return False
|
|
28
288
|
try:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
fn_line = getattr(code, "co_firstlineno", None) if code else None
|
|
33
|
-
return (mod, qn, fn_line)
|
|
289
|
+
if getattr(proto, "__runtime_protocol__", False) or getattr(proto, "__annotations__", None) is not None:
|
|
290
|
+
inst = object.__new__(typ)
|
|
291
|
+
return isinstance(inst, proto)
|
|
34
292
|
except Exception:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
293
|
+
pass
|
|
294
|
+
for name, val in proto.__dict__.items():
|
|
295
|
+
if name.startswith("_") or not callable(val):
|
|
296
|
+
continue
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
def _collect_by_type(locator: ComponentLocator, t: type, q: Optional[str]):
|
|
300
|
+
keys = list(locator._metadata.keys())
|
|
301
|
+
out: List[KeyT] = []
|
|
302
|
+
for k in keys:
|
|
303
|
+
md = locator._metadata.get(k)
|
|
304
|
+
if md is None:
|
|
305
|
+
continue
|
|
306
|
+
typ = md.provided_type or md.concrete_class
|
|
307
|
+
if not isinstance(typ, type):
|
|
308
|
+
continue
|
|
309
|
+
ok = False
|
|
310
|
+
try:
|
|
311
|
+
ok = issubclass(typ, t)
|
|
312
|
+
except Exception:
|
|
313
|
+
ok = _implements_protocol(typ, t)
|
|
314
|
+
if ok and (q is None or q in md.qualifiers):
|
|
315
|
+
out.append(k)
|
|
316
|
+
return out
|
|
317
|
+
|
|
318
|
+
def _find_key_by_name(locator: Optional[ComponentLocator], name: str) -> Optional[KeyT]:
|
|
319
|
+
if not locator:
|
|
320
|
+
return None
|
|
321
|
+
for k, md in locator._metadata.items():
|
|
322
|
+
if md.pico_name == name:
|
|
323
|
+
return k
|
|
324
|
+
typ = md.provided_type or md.concrete_class
|
|
325
|
+
if isinstance(typ, type) and getattr(typ, "__name__", "") == name:
|
|
326
|
+
return k
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
def _normalize_callable(obj):
|
|
330
|
+
return getattr(obj, '__func__', obj)
|
|
331
|
+
|
|
332
|
+
def _get_signature_safe(callable_obj):
|
|
333
|
+
try:
|
|
334
|
+
return inspect.signature(callable_obj)
|
|
335
|
+
except (ValueError, TypeError):
|
|
336
|
+
wrapped = getattr(callable_obj, '__wrapped__', None)
|
|
337
|
+
if wrapped is not None:
|
|
338
|
+
return inspect.signature(wrapped)
|
|
339
|
+
raise
|
|
340
|
+
|
|
341
|
+
@functools.lru_cache(maxsize=1024)
|
|
342
|
+
def _analyze_signature_template(callable_obj):
|
|
343
|
+
callable_obj = _normalize_callable(callable_obj)
|
|
344
|
+
sig = _get_signature_safe(callable_obj)
|
|
345
|
+
plan = []
|
|
346
|
+
for name, param in sig.parameters.items():
|
|
347
|
+
if name in ("self", "cls"): continue
|
|
348
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): continue
|
|
349
|
+
ann = param.annotation
|
|
350
|
+
is_list, elem_t, qual = _extract_list_req(ann)
|
|
351
|
+
if is_list:
|
|
352
|
+
plan.append(("list", name, (elem_t, qual)))
|
|
353
|
+
else:
|
|
354
|
+
plan.append(("key", name, ann))
|
|
355
|
+
return tuple(plan)
|
|
356
|
+
|
|
357
|
+
def _compile_argplan(callable_obj, pico):
|
|
358
|
+
template = _analyze_signature_template(callable_obj)
|
|
359
|
+
locator = pico._locator
|
|
57
360
|
items = []
|
|
58
|
-
for
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _make_fingerprint_from_signature(locals_in_init: dict) -> tuple:
|
|
65
|
-
sig = _inspect.signature(init)
|
|
66
|
-
entries = []
|
|
67
|
-
for name in sig.parameters.keys():
|
|
68
|
-
if name in _FP_EXCLUDE_KEYS: continue
|
|
69
|
-
if name == "root_package":
|
|
70
|
-
rp = locals_in_init.get("root_package")
|
|
71
|
-
root_name = rp if isinstance(rp, str) else getattr(rp, "__name__", None)
|
|
72
|
-
entries.append(("root", root_name))
|
|
361
|
+
for kind, name, data in template:
|
|
362
|
+
if kind == "list":
|
|
363
|
+
elem_t, qual = data
|
|
364
|
+
if locator is not None and isinstance(elem_t, type):
|
|
365
|
+
keys = _collect_by_type(locator, elem_t, qual)
|
|
366
|
+
items.append(("list", name, tuple(keys)))
|
|
73
367
|
continue
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
elif
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
368
|
+
ann = data
|
|
369
|
+
if ann is not inspect._empty and isinstance(ann, type):
|
|
370
|
+
key = ann
|
|
371
|
+
elif ann is not inspect._empty and isinstance(ann, str):
|
|
372
|
+
mapped = _find_key_by_name(locator, ann)
|
|
373
|
+
key = mapped if mapped is not None else ann
|
|
374
|
+
else:
|
|
375
|
+
key = name
|
|
376
|
+
items.append(("key", name, key))
|
|
377
|
+
return tuple(items)
|
|
378
|
+
|
|
379
|
+
def _resolve_args(callable_obj: Callable[..., Any], pico: "PicoContainer") -> Dict[str, Any]:
|
|
380
|
+
plan = _compile_argplan(callable_obj, pico)
|
|
381
|
+
kwargs: Dict[str, Any] = {}
|
|
382
|
+
tracer = pico._tracer
|
|
383
|
+
prev = tracer.override_via("constructor")
|
|
384
|
+
try:
|
|
385
|
+
for kind, name, data in plan:
|
|
386
|
+
if kind == "key":
|
|
387
|
+
tracer.note_param(data if isinstance(data, (str, type)) else name, name)
|
|
388
|
+
kwargs[name] = pico.get(data)
|
|
389
|
+
else:
|
|
390
|
+
vals = [pico.get(k) for k in data]
|
|
391
|
+
kwargs[name] = vals
|
|
392
|
+
finally:
|
|
393
|
+
tracer.restore_via(prev)
|
|
394
|
+
return kwargs
|
|
395
|
+
|
|
396
|
+
def _needs_async_configure(obj: Any) -> bool:
|
|
397
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
398
|
+
meta = getattr(m, PICO_META, {})
|
|
399
|
+
if meta.get("configure", False) and inspect.iscoroutinefunction(m):
|
|
400
|
+
return True
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
def _iter_configure_methods(obj: Any):
|
|
404
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
405
|
+
meta = getattr(m, PICO_META, {})
|
|
406
|
+
if meta.get("configure", False):
|
|
407
|
+
yield m
|
|
408
|
+
|
|
409
|
+
def _build_class(cls: type, pico: "PicoContainer", locator: ComponentLocator) -> Any:
|
|
410
|
+
init = cls.__init__
|
|
411
|
+
if init is object.__init__:
|
|
412
|
+
inst = cls()
|
|
413
|
+
else:
|
|
414
|
+
deps = _resolve_args(init, pico)
|
|
415
|
+
inst = cls(**deps)
|
|
416
|
+
ainit = getattr(inst, "__ainit__", None)
|
|
417
|
+
has_async = (callable(ainit) and inspect.iscoroutinefunction(ainit)) or _needs_async_configure(inst)
|
|
418
|
+
if has_async:
|
|
419
|
+
async def runner():
|
|
420
|
+
if callable(ainit):
|
|
421
|
+
kwargs = {}
|
|
87
422
|
try:
|
|
88
|
-
|
|
89
|
-
norm.append(("env", getattr(s, "prefix", "")))
|
|
90
|
-
elif type(s).__name__ == "FileSource":
|
|
91
|
-
norm.append(("file", str(getattr(s, "path", ""))))
|
|
92
|
-
else:
|
|
93
|
-
norm.append((type(s).__module__, type(s).__qualname__))
|
|
423
|
+
kwargs = _resolve_args(ainit, pico)
|
|
94
424
|
except Exception:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
425
|
+
kwargs = {}
|
|
426
|
+
res = ainit(**kwargs)
|
|
427
|
+
if inspect.isawaitable(res):
|
|
428
|
+
await res
|
|
429
|
+
for m in _iter_configure_methods(inst):
|
|
430
|
+
args = _resolve_args(m, pico)
|
|
431
|
+
r = m(**args)
|
|
432
|
+
if inspect.isawaitable(r):
|
|
433
|
+
await r
|
|
434
|
+
return inst
|
|
435
|
+
return runner()
|
|
436
|
+
for m in _iter_configure_methods(inst):
|
|
437
|
+
args = _resolve_args(m, pico)
|
|
438
|
+
m(**args)
|
|
439
|
+
return inst
|
|
440
|
+
|
|
441
|
+
def _build_method(fn: Callable[..., Any], pico: "PicoContainer", locator: ComponentLocator) -> Any:
|
|
442
|
+
deps = _resolve_args(fn, pico)
|
|
443
|
+
obj = fn(**deps)
|
|
444
|
+
has_async = _needs_async_configure(obj)
|
|
445
|
+
if has_async:
|
|
446
|
+
async def runner():
|
|
447
|
+
for m in _iter_configure_methods(obj):
|
|
448
|
+
args = _resolve_args(m, pico)
|
|
449
|
+
r = m(**args)
|
|
450
|
+
if inspect.isawaitable(r):
|
|
451
|
+
await r
|
|
452
|
+
return obj
|
|
453
|
+
return runner()
|
|
454
|
+
for m in _iter_configure_methods(obj):
|
|
455
|
+
args = _resolve_args(m, pico)
|
|
456
|
+
m(**args)
|
|
457
|
+
return obj
|
|
108
458
|
|
|
109
|
-
def
|
|
110
|
-
exclude: Optional[Callable[[str], bool]], auto_exclude_caller: bool, *, root_name: Optional[str] = None
|
|
111
|
-
) -> Optional[Callable[[str], bool]]:
|
|
112
|
-
if not auto_exclude_caller: return exclude
|
|
113
|
-
caller = _get_caller_module_name()
|
|
114
|
-
if not caller: return exclude
|
|
115
|
-
def _under_root(mod: str) -> bool:
|
|
116
|
-
return bool(root_name) and (mod == root_name or mod.startswith(root_name + "."))
|
|
117
|
-
if exclude is None:
|
|
118
|
-
return lambda mod, _caller=caller: (mod == _caller) and not _under_root(mod)
|
|
119
|
-
return lambda mod, _caller=caller, _prev=exclude: (((mod == _caller) and not _under_root(mod)) or _prev(mod))
|
|
120
|
-
|
|
121
|
-
def _get_caller_module_name() -> Optional[str]:
|
|
459
|
+
def _get_return_type(fn: Callable[..., Any]) -> Optional[type]:
|
|
122
460
|
try:
|
|
123
|
-
|
|
124
|
-
# Stack: _get_caller -> _build_exclude -> init -> caller
|
|
125
|
-
if f and f.f_back and f.f_back.f_back and f.f_back.f_back.f_back:
|
|
126
|
-
mod = _inspect.getmodule(f.f_back.f_back.f_back)
|
|
127
|
-
return getattr(mod, "__name__", None)
|
|
461
|
+
ra = inspect.signature(fn).return_annotation
|
|
128
462
|
except Exception:
|
|
129
|
-
|
|
130
|
-
|
|
463
|
+
return None
|
|
464
|
+
if ra is inspect._empty:
|
|
465
|
+
return None
|
|
466
|
+
return ra if isinstance(ra, type) else None
|
|
467
|
+
|
|
468
|
+
def _scan_package(package) -> Iterable[Any]:
|
|
469
|
+
for _, name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
|
|
470
|
+
yield importlib.import_module(name)
|
|
471
|
+
|
|
472
|
+
def _iter_input_modules(inputs: Union[Any, Iterable[Any]]) -> Iterable[Any]:
|
|
473
|
+
seq = inputs if isinstance(inputs, Iterable) and not inspect.ismodule(inputs) and not isinstance(inputs, str) else [inputs]
|
|
474
|
+
seen: Set[str] = set()
|
|
475
|
+
for it in seq:
|
|
476
|
+
if isinstance(it, str):
|
|
477
|
+
mod = importlib.import_module(it)
|
|
478
|
+
else:
|
|
479
|
+
mod = it
|
|
480
|
+
if hasattr(mod, "__path__"):
|
|
481
|
+
for sub in _scan_package(mod):
|
|
482
|
+
name = getattr(sub, "__name__", None)
|
|
483
|
+
if name and name not in seen:
|
|
484
|
+
seen.add(name)
|
|
485
|
+
yield sub
|
|
486
|
+
else:
|
|
487
|
+
name = getattr(mod, "__name__", None)
|
|
488
|
+
if name and name not in seen:
|
|
489
|
+
seen.add(name)
|
|
490
|
+
yield mod
|
|
491
|
+
|
|
492
|
+
def _can_be_selected_for(reg_md: Dict[KeyT, ProviderMetadata], selector: Any) -> bool:
|
|
493
|
+
if not isinstance(selector, type):
|
|
494
|
+
return False
|
|
495
|
+
for md in reg_md.values():
|
|
496
|
+
typ = md.provided_type or md.concrete_class
|
|
497
|
+
if isinstance(typ, type):
|
|
498
|
+
try:
|
|
499
|
+
if issubclass(typ, selector):
|
|
500
|
+
return True
|
|
501
|
+
except Exception:
|
|
502
|
+
continue
|
|
503
|
+
return False
|
|
504
|
+
|
|
505
|
+
def _normalize_override_provider(v: Any) -> Tuple[Provider, bool]:
|
|
506
|
+
if isinstance(v, tuple) and len(v) == 2:
|
|
507
|
+
src, lz = v
|
|
508
|
+
if callable(src):
|
|
509
|
+
return (lambda s=src: s()), bool(lz)
|
|
510
|
+
return (lambda s=src: s), bool(lz)
|
|
511
|
+
if callable(v):
|
|
512
|
+
return (lambda f=v: f()), False
|
|
513
|
+
return (lambda inst=v: inst), False
|
|
514
|
+
|
|
515
|
+
class Registrar:
|
|
516
|
+
def __init__(
|
|
517
|
+
self,
|
|
518
|
+
factory: ComponentFactory,
|
|
519
|
+
*,
|
|
520
|
+
profiles: Tuple[str, ...] = (),
|
|
521
|
+
environ: Optional[Dict[str, str]] = None,
|
|
522
|
+
logger: Optional[logging.Logger] = None,
|
|
523
|
+
config: Tuple[ConfigSource, ...] = (),
|
|
524
|
+
tree_sources: Tuple["TreeSource", ...] = ()
|
|
525
|
+
) -> None:
|
|
526
|
+
self._factory = factory
|
|
527
|
+
self._profiles = set(p.strip() for p in profiles if p)
|
|
528
|
+
self._environ = environ if environ is not None else os.environ
|
|
529
|
+
self._deferred: List[DeferredProvider] = []
|
|
530
|
+
self._candidates: Dict[KeyT, List[Tuple[bool, Provider, ProviderMetadata]]] = {}
|
|
531
|
+
self._metadata: Dict[KeyT, ProviderMetadata] = {}
|
|
532
|
+
self._indexes: Dict[str, Dict[Any, List[KeyT]]] = {}
|
|
533
|
+
self._on_missing: List[Tuple[int, KeyT, type]] = []
|
|
534
|
+
self._log = logger or LOGGER
|
|
535
|
+
self._config_sources: Tuple[ConfigSource, ...] = tuple(config)
|
|
536
|
+
from .config_runtime import ConfigResolver, TypeAdapterRegistry, ObjectGraphBuilder
|
|
537
|
+
self._resolver = ConfigResolver(tuple(tree_sources))
|
|
538
|
+
self._adapters = TypeAdapterRegistry()
|
|
539
|
+
self._graph = ObjectGraphBuilder(self._resolver, self._adapters)
|
|
540
|
+
self._provides_functions: Dict[KeyT, Callable[..., Any]] = {}
|
|
541
|
+
|
|
542
|
+
def locator(self) -> ComponentLocator:
|
|
543
|
+
loc = ComponentLocator(self._metadata, self._indexes)
|
|
544
|
+
setattr(loc, "_provides_functions", dict(self._provides_functions))
|
|
545
|
+
return loc
|
|
546
|
+
|
|
547
|
+
def attach_runtime(self, pico, locator: ComponentLocator) -> None:
|
|
548
|
+
for deferred in self._deferred:
|
|
549
|
+
deferred.attach(pico, locator)
|
|
550
|
+
for key, md in list(self._metadata.items()):
|
|
551
|
+
if md.lazy:
|
|
552
|
+
original = self._factory.get(key)
|
|
553
|
+
def lazy_proxy_provider(_orig=original, _p=pico):
|
|
554
|
+
return UnifiedComponentProxy(container=_p, object_creator=_orig)
|
|
555
|
+
self._factory.bind(key, lazy_proxy_provider)
|
|
131
556
|
|
|
132
|
-
def
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
) ->
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
557
|
+
def _queue(self, key: KeyT, provider: Provider, md: ProviderMetadata) -> None:
|
|
558
|
+
lst = self._candidates.setdefault(key, [])
|
|
559
|
+
lst.append((md.primary, provider, md))
|
|
560
|
+
if isinstance(provider, DeferredProvider):
|
|
561
|
+
self._deferred.append(provider)
|
|
562
|
+
|
|
563
|
+
def _bind_if_absent(self, key: KeyT, provider: Provider) -> None:
|
|
564
|
+
if not self._factory.has(key):
|
|
565
|
+
self._factory.bind(key, provider)
|
|
566
|
+
|
|
567
|
+
def _enabled_by_condition(self, obj: Any) -> bool:
|
|
568
|
+
meta = getattr(obj, PICO_META, {})
|
|
569
|
+
c = meta.get("conditional", None)
|
|
570
|
+
if not c:
|
|
571
|
+
return True
|
|
572
|
+
p = set(c.get("profiles") or ())
|
|
573
|
+
if p and not (p & self._profiles):
|
|
574
|
+
return False
|
|
575
|
+
req = c.get("require_env") or ()
|
|
576
|
+
for k in req:
|
|
577
|
+
if k not in self._environ or not self._environ.get(k):
|
|
578
|
+
return False
|
|
579
|
+
pred = c.get("predicate")
|
|
580
|
+
if pred is None:
|
|
581
|
+
return True
|
|
582
|
+
try:
|
|
583
|
+
ok = bool(pred())
|
|
584
|
+
except Exception:
|
|
585
|
+
return False
|
|
586
|
+
if not ok:
|
|
587
|
+
return False
|
|
588
|
+
return True
|
|
589
|
+
|
|
590
|
+
def _register_component_class(self, cls: type) -> None:
|
|
591
|
+
if not self._enabled_by_condition(cls):
|
|
592
|
+
return
|
|
593
|
+
key = getattr(cls, PICO_KEY, cls)
|
|
594
|
+
provider = DeferredProvider(lambda pico, loc, c=cls: _build_class(c, pico, loc))
|
|
595
|
+
qset = set(str(q) for q in getattr(cls, PICO_META, {}).get("qualifier", ()))
|
|
596
|
+
sc = getattr(cls, PICO_META, {}).get("scope", "singleton")
|
|
597
|
+
md = ProviderMetadata(key=key, provided_type=cls, concrete_class=cls, factory_class=None, factory_method=None, qualifiers=qset, primary=bool(getattr(cls, PICO_META, {}).get("primary")), lazy=bool(getattr(cls, PICO_META, {}).get("lazy", False)), infra=getattr(cls, PICO_INFRA, None), pico_name=getattr(cls, PICO_NAME, None), scope=sc)
|
|
598
|
+
self._queue(key, provider, md)
|
|
599
|
+
|
|
600
|
+
def _register_factory_class(self, cls: type) -> None:
|
|
601
|
+
if not self._enabled_by_condition(cls):
|
|
602
|
+
return
|
|
603
|
+
for name in dir(cls):
|
|
160
604
|
try:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
605
|
+
raw = inspect.getattr_static(cls, name)
|
|
606
|
+
except Exception:
|
|
607
|
+
continue
|
|
608
|
+
fn = None
|
|
609
|
+
kind = None
|
|
610
|
+
if isinstance(raw, staticmethod):
|
|
611
|
+
fn = raw.__func__
|
|
612
|
+
kind = "static"
|
|
613
|
+
elif isinstance(raw, classmethod):
|
|
614
|
+
fn = raw.__func__
|
|
615
|
+
kind = "class"
|
|
616
|
+
elif inspect.isfunction(raw):
|
|
617
|
+
fn = raw
|
|
618
|
+
kind = "instance"
|
|
619
|
+
else:
|
|
620
|
+
continue
|
|
621
|
+
if getattr(fn, PICO_INFRA, None) != "provides":
|
|
622
|
+
continue
|
|
623
|
+
if not self._enabled_by_condition(fn):
|
|
624
|
+
continue
|
|
625
|
+
k = getattr(fn, PICO_KEY)
|
|
626
|
+
if kind == "instance":
|
|
627
|
+
provider = DeferredProvider(lambda pico, loc, fc=cls, mn=name: _build_method(getattr(_build_class(fc, pico, loc), mn), pico, loc))
|
|
628
|
+
else:
|
|
629
|
+
provider = DeferredProvider(lambda pico, loc, f=fn: _build_method(f, pico, loc))
|
|
630
|
+
rt = _get_return_type(fn)
|
|
631
|
+
qset = set(str(q) for q in getattr(fn, PICO_META, {}).get("qualifier", ()))
|
|
632
|
+
sc = getattr(fn, PICO_META, {}).get("scope", getattr(cls, PICO_META, {}).get("scope", "singleton"))
|
|
633
|
+
md = ProviderMetadata(
|
|
634
|
+
key=k,
|
|
635
|
+
provided_type=rt if isinstance(rt, type) else (k if isinstance(k, type) else None),
|
|
636
|
+
concrete_class=None,
|
|
637
|
+
factory_class=cls,
|
|
638
|
+
factory_method=name,
|
|
639
|
+
qualifiers=qset,
|
|
640
|
+
primary=bool(getattr(fn, PICO_META, {}).get("primary")),
|
|
641
|
+
lazy=bool(getattr(fn, PICO_META, {}).get("lazy", False)),
|
|
642
|
+
infra=getattr(cls, PICO_INFRA, None),
|
|
643
|
+
pico_name=getattr(fn, PICO_NAME, None),
|
|
644
|
+
scope=sc
|
|
645
|
+
)
|
|
646
|
+
self._queue(k, provider, md)
|
|
647
|
+
|
|
648
|
+
def _register_configuration_class(self, cls: type) -> None:
|
|
649
|
+
if not self._enabled_by_condition(cls):
|
|
650
|
+
return
|
|
651
|
+
pref = getattr(cls, PICO_META, {}).get("config_prefix", None)
|
|
652
|
+
if is_dataclass(cls):
|
|
653
|
+
key = cls
|
|
654
|
+
provider = DeferredProvider(lambda pico, loc, c=cls, p=pref, src=self._config_sources: _build_settings_instance(c, src, p))
|
|
655
|
+
md = ProviderMetadata(key=key, provided_type=cls, concrete_class=cls, factory_class=None, factory_method=None, qualifiers=set(), primary=True, lazy=False, infra="configuration", pico_name=getattr(cls, PICO_NAME, None), scope="singleton")
|
|
656
|
+
self._queue(key, provider, md)
|
|
657
|
+
|
|
658
|
+
def _register_configured_class(self, cls: type) -> None:
|
|
659
|
+
if not self._enabled_by_condition(cls):
|
|
660
|
+
return
|
|
661
|
+
meta = getattr(cls, PICO_META, {})
|
|
662
|
+
cfg = meta.get("configured", None)
|
|
663
|
+
if not cfg:
|
|
664
|
+
return
|
|
665
|
+
target = cfg.get("target")
|
|
666
|
+
prefix = cfg.get("prefix")
|
|
667
|
+
if not isinstance(target, type):
|
|
668
|
+
return
|
|
669
|
+
provider = DeferredProvider(lambda pico, loc, t=target, p=prefix, g=self._graph: g.build_from_prefix(t, p))
|
|
670
|
+
qset = set(str(q) for q in meta.get("qualifier", ()))
|
|
671
|
+
sc = meta.get("scope", "singleton")
|
|
672
|
+
md = ProviderMetadata(key=target, provided_type=target, concrete_class=None, factory_class=None, factory_method=None, qualifiers=qset, primary=True, lazy=False, infra="configured", pico_name=prefix, scope=sc)
|
|
673
|
+
self._queue(target, provider, md)
|
|
674
|
+
|
|
675
|
+
def _register_provides_function(self, fn: Callable[..., Any]) -> None:
|
|
676
|
+
if not self._enabled_by_condition(fn):
|
|
677
|
+
return
|
|
678
|
+
k = getattr(fn, PICO_KEY)
|
|
679
|
+
provider = DeferredProvider(lambda pico, loc, f=fn: _build_method(f, pico, loc))
|
|
680
|
+
rt = _get_return_type(fn)
|
|
681
|
+
qset = set(str(q) for q in getattr(fn, PICO_META, {}).get("qualifier", ()))
|
|
682
|
+
sc = getattr(fn, PICO_META, {}).get("scope", "singleton")
|
|
683
|
+
md = ProviderMetadata(
|
|
684
|
+
key=k,
|
|
685
|
+
provided_type=rt if isinstance(rt, type) else (k if isinstance(k, type) else None),
|
|
686
|
+
concrete_class=None,
|
|
687
|
+
factory_class=None,
|
|
688
|
+
factory_method=getattr(fn, "__name__", None),
|
|
689
|
+
qualifiers=qset,
|
|
690
|
+
primary=bool(getattr(fn, PICO_META, {}).get("primary")),
|
|
691
|
+
lazy=bool(getattr(fn, PICO_META, {}).get("lazy", False)),
|
|
692
|
+
infra="provides",
|
|
693
|
+
pico_name=getattr(fn, PICO_NAME, None),
|
|
694
|
+
scope=sc
|
|
695
|
+
)
|
|
696
|
+
self._queue(k, provider, md)
|
|
697
|
+
self._provides_functions[k] = fn
|
|
698
|
+
|
|
699
|
+
def register_module(self, module: Any) -> None:
|
|
700
|
+
for _, obj in inspect.getmembers(module):
|
|
701
|
+
if inspect.isclass(obj):
|
|
702
|
+
meta = getattr(obj, PICO_META, {})
|
|
703
|
+
if "on_missing" in meta:
|
|
704
|
+
sel = meta["on_missing"]["selector"]
|
|
705
|
+
pr = int(meta["on_missing"].get("priority", 0))
|
|
706
|
+
self._on_missing.append((pr, sel, obj))
|
|
707
|
+
continue
|
|
708
|
+
infra = getattr(obj, PICO_INFRA, None)
|
|
709
|
+
if infra == "component":
|
|
710
|
+
self._register_component_class(obj)
|
|
711
|
+
elif infra == "factory":
|
|
712
|
+
self._register_factory_class(obj)
|
|
713
|
+
elif infra == "configuration":
|
|
714
|
+
self._register_configuration_class(obj)
|
|
715
|
+
elif infra == "configured":
|
|
716
|
+
self._register_configured_class(obj)
|
|
717
|
+
for _, fn in inspect.getmembers(module, predicate=inspect.isfunction):
|
|
718
|
+
if getattr(fn, PICO_INFRA, None) == "provides":
|
|
719
|
+
self._register_provides_function(fn)
|
|
720
|
+
|
|
721
|
+
def _prefix_exists(self, md: ProviderMetadata) -> bool:
|
|
722
|
+
if md.infra != "configured":
|
|
723
|
+
return False
|
|
724
|
+
try:
|
|
725
|
+
_ = self._resolver.subtree(md.pico_name)
|
|
726
|
+
return True
|
|
727
|
+
except Exception:
|
|
728
|
+
return False
|
|
729
|
+
|
|
730
|
+
def select_and_bind(self) -> None:
|
|
731
|
+
for key, lst in self._candidates.items():
|
|
732
|
+
def rank(item: Tuple[bool, Provider, ProviderMetadata]) -> Tuple[int, int, int]:
|
|
733
|
+
is_present = 1 if self._prefix_exists(item[2]) else 0
|
|
734
|
+
pref = str(item[2].pico_name or "")
|
|
735
|
+
pref_len = len(pref)
|
|
736
|
+
is_primary = 1 if item[0] else 0
|
|
737
|
+
return (is_present, pref_len, is_primary)
|
|
738
|
+
lst_sorted = sorted(lst, key=rank, reverse=True)
|
|
739
|
+
chosen = lst_sorted[0]
|
|
740
|
+
self._bind_if_absent(key, chosen[1])
|
|
741
|
+
self._metadata[key] = chosen[2]
|
|
742
|
+
|
|
743
|
+
def _find_md_for_type(self, t: type) -> Optional[ProviderMetadata]:
|
|
744
|
+
cands: List[ProviderMetadata] = []
|
|
745
|
+
for md in self._metadata.values():
|
|
746
|
+
typ = md.provided_type or md.concrete_class
|
|
747
|
+
if not isinstance(typ, type):
|
|
748
|
+
continue
|
|
207
749
|
try:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
750
|
+
if issubclass(typ, t):
|
|
751
|
+
cands.append(md)
|
|
752
|
+
except Exception:
|
|
753
|
+
continue
|
|
754
|
+
if not cands:
|
|
755
|
+
return None
|
|
756
|
+
prim = [m for m in cands if m.primary]
|
|
757
|
+
return prim[0] if prim else cands[0]
|
|
758
|
+
|
|
759
|
+
def _iter_param_types(self, callable_obj: Callable[..., Any]) -> Iterable[type]:
|
|
760
|
+
sig = inspect.signature(callable_obj)
|
|
761
|
+
for name, param in sig.parameters.items():
|
|
762
|
+
if name in ("self", "cls"):
|
|
763
|
+
continue
|
|
764
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
765
|
+
continue
|
|
766
|
+
ann = param.annotation
|
|
767
|
+
is_list, elem_t, _ = _extract_list_req(ann)
|
|
768
|
+
t = elem_t if is_list else (ann if isinstance(ann, type) else None)
|
|
769
|
+
if isinstance(t, type):
|
|
770
|
+
yield t
|
|
771
|
+
|
|
772
|
+
def _infer_narrower_scope(self, md: ProviderMetadata) -> Optional[str]:
|
|
773
|
+
if md.concrete_class is not None:
|
|
774
|
+
init = md.concrete_class.__init__
|
|
775
|
+
for t in self._iter_param_types(init):
|
|
776
|
+
dep = self._find_md_for_type(t)
|
|
777
|
+
if dep and dep.scope != "singleton":
|
|
778
|
+
return dep.scope
|
|
779
|
+
if md.factory_class is not None and md.factory_method is not None:
|
|
780
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
781
|
+
for t in self._iter_param_types(fn):
|
|
782
|
+
dep = self._find_md_for_type(t)
|
|
783
|
+
if dep and dep.scope != "singleton":
|
|
784
|
+
return dep.scope
|
|
785
|
+
if md.infra == "provides":
|
|
786
|
+
fn = self._provides_functions.get(md.key)
|
|
787
|
+
if callable(fn):
|
|
788
|
+
for t in self._iter_param_types(fn):
|
|
789
|
+
dep = self._find_md_for_type(t)
|
|
790
|
+
if dep and dep.scope != "singleton":
|
|
791
|
+
return dep.scope
|
|
792
|
+
return None
|
|
793
|
+
|
|
794
|
+
def _promote_scopes(self) -> None:
|
|
795
|
+
for k, md in list(self._metadata.items()):
|
|
796
|
+
if md.scope == "singleton":
|
|
797
|
+
ns = self._infer_narrower_scope(md)
|
|
798
|
+
if ns and ns != "singleton":
|
|
799
|
+
self._metadata[k] = ProviderMetadata(
|
|
800
|
+
key=md.key,
|
|
801
|
+
provided_type=md.provided_type,
|
|
802
|
+
concrete_class=md.concrete_class,
|
|
803
|
+
factory_class=md.factory_class,
|
|
804
|
+
factory_method=md.factory_method,
|
|
805
|
+
qualifiers=md.qualifiers,
|
|
806
|
+
primary=md.primary,
|
|
807
|
+
lazy=md.lazy,
|
|
808
|
+
infra=md.infra,
|
|
809
|
+
pico_name=md.pico_name,
|
|
810
|
+
override=md.override,
|
|
811
|
+
scope=ns
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
def _rebuild_indexes(self) -> None:
|
|
815
|
+
self._indexes.clear()
|
|
816
|
+
def add(idx: str, val: Any, key: KeyT):
|
|
817
|
+
b = self._indexes.setdefault(idx, {}).setdefault(val, [])
|
|
818
|
+
if key not in b:
|
|
819
|
+
b.append(key)
|
|
820
|
+
for k, md in self._metadata.items():
|
|
821
|
+
for q in md.qualifiers:
|
|
822
|
+
add("qualifier", q, k)
|
|
823
|
+
if md.primary:
|
|
824
|
+
add("primary", True, k)
|
|
825
|
+
add("lazy", bool(md.lazy), k)
|
|
826
|
+
if md.infra is not None:
|
|
827
|
+
add("infra", md.infra, k)
|
|
828
|
+
if md.pico_name is not None:
|
|
829
|
+
add("pico_name", md.pico_name, k)
|
|
830
|
+
|
|
831
|
+
def _find_md_for_name(self, name: str) -> Optional[KeyT]:
|
|
832
|
+
for k, md in self._metadata.items():
|
|
833
|
+
if md.pico_name == name:
|
|
834
|
+
return k
|
|
835
|
+
t = md.provided_type or md.concrete_class
|
|
836
|
+
if isinstance(t, type) and getattr(t, "__name__", "") == name:
|
|
837
|
+
return k
|
|
838
|
+
return None
|
|
839
|
+
|
|
840
|
+
def _validate_bindings(self) -> None:
|
|
841
|
+
errors: List[str] = []
|
|
842
|
+
|
|
843
|
+
def _fmt(k: KeyT) -> str:
|
|
844
|
+
return getattr(k, '__name__', str(k))
|
|
845
|
+
|
|
846
|
+
def _skip_type(t: type) -> bool:
|
|
847
|
+
if t in (str, int, float, bool, bytes):
|
|
848
|
+
return True
|
|
849
|
+
if t is Any:
|
|
850
|
+
return True
|
|
851
|
+
if getattr(t, "_is_protocol", False):
|
|
852
|
+
return True
|
|
853
|
+
return False
|
|
854
|
+
|
|
855
|
+
def _should_validate(param: inspect.Parameter) -> bool:
|
|
856
|
+
if param.default is not inspect._empty:
|
|
857
|
+
return False
|
|
858
|
+
ann = param.annotation
|
|
859
|
+
origin = get_origin(ann)
|
|
860
|
+
if origin is Union:
|
|
861
|
+
args = get_args(ann)
|
|
862
|
+
if type(None) in args:
|
|
863
|
+
return False
|
|
864
|
+
return True
|
|
865
|
+
|
|
866
|
+
loc = ComponentLocator(self._metadata, self._indexes)
|
|
867
|
+
|
|
868
|
+
for k, md in self._metadata.items():
|
|
869
|
+
if md.infra == "configuration":
|
|
870
|
+
continue
|
|
871
|
+
|
|
872
|
+
callables_to_check: List[Callable[..., Any]] = []
|
|
873
|
+
loc_name = "unknown"
|
|
874
|
+
|
|
875
|
+
if md.concrete_class is not None:
|
|
876
|
+
callables_to_check.append(md.concrete_class.__init__)
|
|
877
|
+
loc_name = "constructor"
|
|
878
|
+
elif md.factory_class is not None and md.factory_method is not None:
|
|
879
|
+
try:
|
|
880
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
881
|
+
callables_to_check.append(fn)
|
|
882
|
+
loc_name = f"factory {_fmt(md.factory_class)}.{md.factory_method}"
|
|
883
|
+
except AttributeError:
|
|
884
|
+
errors.append(f"Component '{_fmt(k)}' refers to missing factory method '{md.factory_method}' on class '{_fmt(md.factory_class)}'")
|
|
885
|
+
continue
|
|
886
|
+
elif md.infra == "provides":
|
|
887
|
+
fn = self._provides_functions.get(k)
|
|
888
|
+
if callable(fn):
|
|
889
|
+
callables_to_check.append(fn)
|
|
890
|
+
loc_name = f"function {getattr(fn, '__name__', 'unknown')}"
|
|
891
|
+
else:
|
|
892
|
+
continue
|
|
893
|
+
else:
|
|
894
|
+
if not md.concrete_class and not md.factory_class and md.override:
|
|
895
|
+
continue
|
|
896
|
+
|
|
897
|
+
for callable_obj in callables_to_check:
|
|
898
|
+
try:
|
|
899
|
+
sig = inspect.signature(callable_obj)
|
|
900
|
+
except (ValueError, TypeError):
|
|
901
|
+
continue
|
|
902
|
+
|
|
903
|
+
for name, param in sig.parameters.items():
|
|
904
|
+
if name in ("self", "cls"):
|
|
905
|
+
continue
|
|
906
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
907
|
+
continue
|
|
908
|
+
if not _should_validate(param):
|
|
909
|
+
continue
|
|
213
910
|
|
|
214
|
-
|
|
215
|
-
|
|
911
|
+
ann = param.annotation
|
|
912
|
+
is_list, elem_t, qual = _extract_list_req(ann)
|
|
216
913
|
|
|
914
|
+
if is_list:
|
|
915
|
+
if qual:
|
|
916
|
+
matching = loc.with_qualifier_any(qual).keys()
|
|
917
|
+
if not matching and isinstance(elem_t, type) and not _skip_type(elem_t):
|
|
918
|
+
errors.append(f"{_fmt(k)} ({loc_name}) expects List[{_fmt(elem_t)}] with qualifier '{qual}' but no matching components exist")
|
|
919
|
+
continue
|
|
920
|
+
|
|
921
|
+
elif isinstance(ann, str):
|
|
922
|
+
key_found_by_name = self._find_md_for_name(ann)
|
|
923
|
+
directly_bound = ann in self._metadata or self._factory.has(ann)
|
|
924
|
+
if not key_found_by_name and not directly_bound:
|
|
925
|
+
errors.append(f"{_fmt(k)} ({loc_name}) depends on string key '{ann}' which is not bound")
|
|
926
|
+
continue
|
|
927
|
+
|
|
928
|
+
elif isinstance(ann, type) and not _skip_type(ann):
|
|
929
|
+
dep_key_found = self._factory.has(ann) or ann in self._metadata
|
|
930
|
+
if not dep_key_found:
|
|
931
|
+
assignable_md = self._find_md_for_type(ann)
|
|
932
|
+
if assignable_md is None:
|
|
933
|
+
by_name_key = self._find_md_for_name(getattr(ann, "__name__", ""))
|
|
934
|
+
if by_name_key is None:
|
|
935
|
+
errors.append(f"{_fmt(k)} ({loc_name}) depends on {_fmt(ann)} which is not bound")
|
|
936
|
+
continue
|
|
937
|
+
|
|
938
|
+
elif ann is inspect._empty:
|
|
939
|
+
name_key = name
|
|
940
|
+
key_found_by_name = self._find_md_for_name(name_key)
|
|
941
|
+
directly_bound = name_key in self._metadata or self._factory.has(name_key)
|
|
942
|
+
if not key_found_by_name and not directly_bound:
|
|
943
|
+
errors.append(f"{_fmt(k)} ({loc_name}) depends on parameter '{name}' with no type hint, and key '{name_key}' is not bound")
|
|
944
|
+
continue
|
|
945
|
+
|
|
946
|
+
if errors:
|
|
947
|
+
raise InvalidBindingError(errors)
|
|
948
|
+
|
|
949
|
+
def finalize(self, overrides: Optional[Dict[KeyT, Any]]) -> None:
|
|
950
|
+
self.select_and_bind()
|
|
951
|
+
self._promote_scopes()
|
|
952
|
+
self._rebuild_indexes()
|
|
953
|
+
for _, selector, default_cls in sorted(self._on_missing, key=lambda x: -x[0]):
|
|
954
|
+
key = selector
|
|
955
|
+
if key in self._metadata or self._factory.has(key) or _can_be_selected_for(self._metadata, selector):
|
|
956
|
+
continue
|
|
957
|
+
provider = DeferredProvider(lambda pico, loc, c=default_cls: _build_class(c, pico, loc))
|
|
958
|
+
qset = set(str(q) for q in getattr(default_cls, PICO_META, {}).get("qualifier", ()))
|
|
959
|
+
sc = getattr(default_cls, PICO_META, {}).get("scope", "singleton")
|
|
960
|
+
md = ProviderMetadata(
|
|
961
|
+
key=key,
|
|
962
|
+
provided_type=key if isinstance(key, type) else None,
|
|
963
|
+
concrete_class=default_cls,
|
|
964
|
+
factory_class=None,
|
|
965
|
+
factory_method=None,
|
|
966
|
+
qualifiers=qset,
|
|
967
|
+
primary=True,
|
|
968
|
+
lazy=bool(getattr(default_cls, PICO_META, {}).get("lazy", False)),
|
|
969
|
+
infra=getattr(default_cls, PICO_INFRA, None),
|
|
970
|
+
pico_name=getattr(default_cls, PICO_NAME, None),
|
|
971
|
+
override=True,
|
|
972
|
+
scope=sc
|
|
973
|
+
)
|
|
974
|
+
self._bind_if_absent(key, provider)
|
|
975
|
+
self._metadata[key] = md
|
|
976
|
+
if isinstance(provider, DeferredProvider):
|
|
977
|
+
self._deferred.append(provider)
|
|
978
|
+
self._rebuild_indexes()
|
|
979
|
+
self._validate_bindings()
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def init(modules: Union[Any, Iterable[Any]], *, profiles: Tuple[str, ...] = (), allowed_profiles: Optional[Iterable[str]] = None, environ: Optional[Dict[str, str]] = None, overrides: Optional[Dict[KeyT, Any]] = None, logger: Optional[logging.Logger] = None, config: Tuple[ConfigSource, ...] = (), custom_scopes: Optional[Dict[str, "ScopeProtocol"]] = None, validate_only: bool = False, container_id: Optional[str] = None, tree_config: Tuple["TreeSource", ...] = (), observers: Optional[List["ContainerObserver"]] = None,) -> PicoContainer:
|
|
983
|
+
active = tuple(p.strip() for p in profiles if p)
|
|
984
|
+
allowed_set = set(a.strip() for a in allowed_profiles) if allowed_profiles is not None else None
|
|
985
|
+
if allowed_set is not None:
|
|
986
|
+
unknown = set(active) - allowed_set
|
|
987
|
+
if unknown:
|
|
988
|
+
raise ConfigurationError(f"Unknown profiles: {sorted(unknown)}; allowed: {sorted(allowed_set)}")
|
|
989
|
+
factory = ComponentFactory()
|
|
990
|
+
caches = ScopedCaches()
|
|
991
|
+
scopes = ScopeManager()
|
|
992
|
+
if custom_scopes:
|
|
993
|
+
for n, impl in custom_scopes.items():
|
|
994
|
+
scopes.register_scope(n, impl)
|
|
995
|
+
pico = PicoContainer(
|
|
996
|
+
factory,
|
|
997
|
+
caches,
|
|
998
|
+
scopes,
|
|
999
|
+
container_id=container_id,
|
|
1000
|
+
profiles=active,
|
|
1001
|
+
observers=observers or [],
|
|
1002
|
+
)
|
|
1003
|
+
registrar = Registrar(factory, profiles=active, environ=environ, logger=logger, config=config, tree_sources=tree_config)
|
|
1004
|
+
for m in _iter_input_modules(modules):
|
|
1005
|
+
registrar.register_module(m)
|
|
1006
|
+
if overrides:
|
|
1007
|
+
for k, v in overrides.items():
|
|
1008
|
+
prov, _ = _normalize_override_provider(v)
|
|
1009
|
+
factory.bind(k, prov)
|
|
1010
|
+
registrar.finalize(overrides)
|
|
1011
|
+
if validate_only:
|
|
1012
|
+
locator = registrar.locator()
|
|
1013
|
+
pico.attach_locator(locator)
|
|
1014
|
+
_fail_fast_cycle_check(pico)
|
|
1015
|
+
return pico
|
|
1016
|
+
locator = registrar.locator()
|
|
1017
|
+
registrar.attach_runtime(pico, locator)
|
|
1018
|
+
pico.attach_locator(locator)
|
|
1019
|
+
_fail_fast_cycle_check(pico)
|
|
1020
|
+
return pico
|
|
1021
|
+
|
|
1022
|
+
def _dependency_keys_for(key: KeyT, pico: "PicoContainer") -> Tuple[KeyT, ...]:
|
|
1023
|
+
loc = pico._locator
|
|
1024
|
+
if not loc:
|
|
1025
|
+
return ()
|
|
1026
|
+
md = loc._metadata.get(key)
|
|
1027
|
+
if not md:
|
|
1028
|
+
return ()
|
|
1029
|
+
if md.concrete_class is not None:
|
|
1030
|
+
fn = md.concrete_class.__init__
|
|
1031
|
+
elif md.factory_class is not None and md.factory_method is not None:
|
|
1032
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
1033
|
+
else:
|
|
1034
|
+
return ()
|
|
1035
|
+
plan = _compile_argplan(fn, pico)
|
|
1036
|
+
deps: List[KeyT] = []
|
|
1037
|
+
for kind, _, data in plan:
|
|
1038
|
+
if kind == "key":
|
|
1039
|
+
deps.append(data)
|
|
1040
|
+
else:
|
|
1041
|
+
for k in data:
|
|
1042
|
+
deps.append(k)
|
|
1043
|
+
return tuple(deps)
|
|
1044
|
+
|
|
1045
|
+
def _get_signature_static(fn: Callable[..., Any]) -> inspect.Signature:
|
|
1046
|
+
return inspect.signature(fn)
|
|
1047
|
+
|
|
1048
|
+
def _compile_argplan_static(callable_obj: Callable[..., Any], locator: ComponentLocator) -> Tuple[Tuple[str, str, Any], ...]:
|
|
1049
|
+
sig = _get_signature_static(callable_obj)
|
|
1050
|
+
items: List[Tuple[str, str, Any]] = []
|
|
1051
|
+
for name, param in sig.parameters.items():
|
|
1052
|
+
if name in ("self", "cls"):
|
|
1053
|
+
continue
|
|
1054
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
1055
|
+
continue
|
|
1056
|
+
ann = param.annotation
|
|
1057
|
+
is_list, elem_t, qual = _extract_list_req(ann)
|
|
1058
|
+
if is_list and locator is not None and isinstance(elem_t, type):
|
|
1059
|
+
keys = _collect_by_type(locator, elem_t, qual)
|
|
1060
|
+
items.append(("list", name, tuple(keys)))
|
|
1061
|
+
continue
|
|
1062
|
+
if ann is not inspect._empty and isinstance(ann, type):
|
|
1063
|
+
key: KeyT = ann
|
|
1064
|
+
elif ann is not inspect._empty and isinstance(ann, str):
|
|
1065
|
+
mapped = _find_key_by_name(locator, ann)
|
|
1066
|
+
key = mapped if mapped is not None else ann
|
|
1067
|
+
else:
|
|
1068
|
+
key = name
|
|
1069
|
+
items.append(("key", name, key))
|
|
1070
|
+
return tuple(items)
|
|
1071
|
+
|
|
1072
|
+
def _dependency_keys_for_static(md: ProviderMetadata, locator: ComponentLocator) -> Tuple[KeyT, ...]:
|
|
1073
|
+
if md.concrete_class is not None:
|
|
1074
|
+
fn = md.concrete_class.__init__
|
|
1075
|
+
elif md.factory_class is not None and md.factory_method is not None:
|
|
1076
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
1077
|
+
elif md.infra == "provides":
|
|
1078
|
+
fn = getattr(locator, "_provides_functions", {}).get(md.key)
|
|
1079
|
+
if not callable(fn):
|
|
1080
|
+
return ()
|
|
1081
|
+
else:
|
|
1082
|
+
return ()
|
|
1083
|
+
plan = _compile_argplan_static(fn, locator)
|
|
1084
|
+
deps: List[KeyT] = []
|
|
1085
|
+
for kind, _, data in plan:
|
|
1086
|
+
if kind == "key":
|
|
1087
|
+
deps.append(data)
|
|
1088
|
+
else:
|
|
1089
|
+
for k in data:
|
|
1090
|
+
deps.append(k)
|
|
1091
|
+
return tuple(deps)
|
|
1092
|
+
|
|
1093
|
+
def _build_resolution_graph(pico: "PicoContainer") -> Dict[KeyT, Tuple[KeyT, ...]]:
|
|
1094
|
+
loc = pico._locator
|
|
1095
|
+
if not loc:
|
|
1096
|
+
return {}
|
|
1097
|
+
|
|
1098
|
+
def _map_dep_to_bound_key(dep_key: KeyT) -> KeyT:
|
|
1099
|
+
if dep_key in loc._metadata:
|
|
1100
|
+
return dep_key
|
|
1101
|
+
if isinstance(dep_key, type):
|
|
1102
|
+
for k, md in loc._metadata.items():
|
|
1103
|
+
typ = md.provided_type or md.concrete_class
|
|
1104
|
+
if isinstance(typ, type):
|
|
1105
|
+
try:
|
|
1106
|
+
if issubclass(typ, dep_key):
|
|
1107
|
+
return k
|
|
1108
|
+
except Exception:
|
|
1109
|
+
continue
|
|
1110
|
+
return dep_key
|
|
1111
|
+
|
|
1112
|
+
graph: Dict[KeyT, Tuple[KeyT, ...]] = {}
|
|
1113
|
+
for key, md in list(loc._metadata.items()):
|
|
1114
|
+
deps: List[KeyT] = []
|
|
1115
|
+
for d in _dependency_keys_for_static(md, loc):
|
|
1116
|
+
mapped = _map_dep_to_bound_key(d)
|
|
1117
|
+
deps.append(mapped)
|
|
1118
|
+
graph[key] = tuple(deps)
|
|
1119
|
+
return graph
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _find_cycle(graph: Dict[KeyT, Tuple[KeyT, ...]]) -> Optional[Tuple[KeyT, ...]]:
|
|
1123
|
+
temp: Set[KeyT] = set()
|
|
1124
|
+
perm: Set[KeyT] = set()
|
|
1125
|
+
stack: List[KeyT] = []
|
|
1126
|
+
def visit(n: KeyT) -> Optional[Tuple[KeyT, ...]]:
|
|
1127
|
+
if n in perm:
|
|
1128
|
+
return None
|
|
1129
|
+
if n in temp:
|
|
1130
|
+
try:
|
|
1131
|
+
idx = stack.index(n)
|
|
1132
|
+
return tuple(stack[idx:] + [n])
|
|
1133
|
+
except ValueError:
|
|
1134
|
+
return tuple([n, n])
|
|
1135
|
+
temp.add(n)
|
|
1136
|
+
stack.append(n)
|
|
1137
|
+
for m in graph.get(n, ()):
|
|
1138
|
+
c = visit(m)
|
|
1139
|
+
if c:
|
|
1140
|
+
return c
|
|
1141
|
+
stack.pop()
|
|
1142
|
+
temp.remove(n)
|
|
1143
|
+
perm.add(n)
|
|
1144
|
+
return None
|
|
1145
|
+
for node in graph.keys():
|
|
1146
|
+
c = visit(node)
|
|
1147
|
+
if c:
|
|
1148
|
+
return c
|
|
1149
|
+
return None
|
|
217
1150
|
|
|
1151
|
+
def _format_key(k: KeyT) -> str:
|
|
1152
|
+
return getattr(k, "__name__", str(k))
|
|
218
1153
|
|
|
219
|
-
def
|
|
220
|
-
|
|
221
|
-
|
|
1154
|
+
def _fail_fast_cycle_check(pico: "PicoContainer") -> None:
|
|
1155
|
+
graph = _build_resolution_graph(pico)
|
|
1156
|
+
cyc = _find_cycle(graph)
|
|
1157
|
+
if not cyc:
|
|
1158
|
+
return
|
|
1159
|
+
path = " -> ".join(_format_key(k) for k in cyc)
|
|
1160
|
+
raise InvalidBindingError([f"Circular dependency detected: {path}"])
|
|
222
1161
|
|