pico-ioc 1.5.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pico_ioc/__init__.py +89 -53
- pico_ioc/_version.py +1 -1
- pico_ioc/aop.py +247 -0
- pico_ioc/api.py +791 -208
- pico_ioc/config_runtime.py +289 -0
- pico_ioc/constants.py +10 -0
- pico_ioc/container.py +289 -152
- pico_ioc/event_bus.py +224 -0
- pico_ioc/exceptions.py +66 -0
- pico_ioc/factory.py +48 -0
- pico_ioc/locator.py +53 -0
- pico_ioc/scope.py +106 -35
- pico_ioc-2.0.0.dist-info/METADATA +230 -0
- pico_ioc-2.0.0.dist-info/RECORD +17 -0
- pico_ioc/_state.py +0 -75
- pico_ioc/builder.py +0 -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.0.dist-info}/WHEEL +0 -0
- {pico_ioc-1.5.0.dist-info → pico_ioc-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-1.5.0.dist-info → pico_ioc-2.0.0.dist-info}/top_level.txt +0 -0
pico_ioc/api.py
CHANGED
|
@@ -1,222 +1,805 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
# src/pico_ioc/api.py
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import inspect
|
|
5
|
+
import functools
|
|
4
6
|
import importlib
|
|
7
|
+
import pkgutil
|
|
5
8
|
import logging
|
|
6
|
-
from
|
|
7
|
-
from typing import Callable, Optional, Tuple,
|
|
8
|
-
|
|
9
|
+
from dataclasses import is_dataclass, fields, dataclass, MISSING
|
|
10
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union, get_args, get_origin, Annotated, Protocol
|
|
11
|
+
from .constants import LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
ProviderNotFoundError,
|
|
14
|
+
CircularDependencyError,
|
|
15
|
+
ComponentCreationError,
|
|
16
|
+
ScopeError,
|
|
17
|
+
ConfigurationError,
|
|
18
|
+
SerializationError,
|
|
19
|
+
InvalidBindingError,
|
|
20
|
+
)
|
|
21
|
+
from .factory import ComponentFactory, ProviderMetadata, DeferredProvider
|
|
22
|
+
from .locator import ComponentLocator
|
|
23
|
+
from .scope import ScopeManager, ScopedCaches
|
|
9
24
|
from .container import PicoContainer
|
|
10
|
-
from .
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
from .aop import UnifiedComponentProxy
|
|
26
|
+
|
|
27
|
+
KeyT = Union[str, type]
|
|
28
|
+
Provider = Callable[[], Any]
|
|
29
|
+
|
|
30
|
+
class ConfigSource(Protocol):
|
|
31
|
+
def get(self, key: str) -> Optional[str]: ...
|
|
32
|
+
|
|
33
|
+
class EnvSource:
|
|
34
|
+
def __init__(self, prefix: str = "") -> None:
|
|
35
|
+
self.prefix = prefix
|
|
36
|
+
def get(self, key: str) -> Optional[str]:
|
|
37
|
+
return os.environ.get(self.prefix + key)
|
|
38
|
+
|
|
39
|
+
class FileSource:
|
|
40
|
+
def __init__(self, path: str, prefix: str = "") -> None:
|
|
41
|
+
self.prefix = prefix
|
|
42
|
+
try:
|
|
43
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
44
|
+
self._data = json.load(f)
|
|
45
|
+
except Exception:
|
|
46
|
+
self._data = {}
|
|
47
|
+
def get(self, key: str) -> Optional[str]:
|
|
48
|
+
k = self.prefix + key
|
|
49
|
+
v = self._data
|
|
50
|
+
for part in k.split("__"):
|
|
51
|
+
if isinstance(v, dict) and part in v:
|
|
52
|
+
v = v[part]
|
|
53
|
+
else:
|
|
54
|
+
return None
|
|
55
|
+
if isinstance(v, (str, int, float, bool)):
|
|
56
|
+
return str(v)
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def _meta_get(obj: Any) -> Dict[str, Any]:
|
|
60
|
+
m = getattr(obj, PICO_META, None)
|
|
61
|
+
if m is None:
|
|
62
|
+
m = {}
|
|
63
|
+
setattr(obj, PICO_META, m)
|
|
64
|
+
return m
|
|
65
|
+
|
|
66
|
+
def component(cls=None, *, name: Any = None):
|
|
67
|
+
def dec(c):
|
|
68
|
+
setattr(c, PICO_INFRA, "component")
|
|
69
|
+
setattr(c, PICO_NAME, name if name is not None else getattr(c, "__name__", str(c)))
|
|
70
|
+
setattr(c, PICO_KEY, name if name is not None else c)
|
|
71
|
+
_meta_get(c)
|
|
72
|
+
return c
|
|
73
|
+
return dec(cls) if cls else dec
|
|
74
|
+
|
|
75
|
+
def factory(cls):
|
|
76
|
+
setattr(cls, PICO_INFRA, "factory")
|
|
77
|
+
setattr(cls, PICO_NAME, getattr(cls, "__name__", str(cls)))
|
|
78
|
+
_meta_get(cls)
|
|
79
|
+
return cls
|
|
80
|
+
|
|
81
|
+
def provides(key: Any):
|
|
82
|
+
def dec(fn):
|
|
83
|
+
@functools.wraps(fn)
|
|
84
|
+
def w(*a, **k):
|
|
85
|
+
return fn(*a, **k)
|
|
86
|
+
setattr(w, PICO_INFRA, "provides")
|
|
87
|
+
setattr(w, PICO_NAME, key)
|
|
88
|
+
setattr(w, PICO_KEY, key)
|
|
89
|
+
_meta_get(w)
|
|
90
|
+
return w
|
|
91
|
+
return dec
|
|
92
|
+
|
|
93
|
+
class Qualifier(str):
|
|
94
|
+
__slots__ = ()
|
|
95
|
+
|
|
96
|
+
def qualifier(*qs: Qualifier):
|
|
97
|
+
def dec(cls):
|
|
98
|
+
m = _meta_get(cls)
|
|
99
|
+
cur = tuple(m.get("qualifier", ()))
|
|
100
|
+
seen = set(cur)
|
|
101
|
+
merged = list(cur)
|
|
102
|
+
for q in qs:
|
|
103
|
+
if q not in seen:
|
|
104
|
+
merged.append(q)
|
|
105
|
+
seen.add(q)
|
|
106
|
+
m["qualifier"] = tuple(merged)
|
|
107
|
+
return cls
|
|
108
|
+
return dec
|
|
109
|
+
|
|
110
|
+
def on_missing(selector: object, *, priority: int = 0):
|
|
111
|
+
def dec(obj):
|
|
112
|
+
m = _meta_get(obj)
|
|
113
|
+
m["on_missing"] = {"selector": selector, "priority": int(priority)}
|
|
114
|
+
return obj
|
|
115
|
+
return dec
|
|
116
|
+
|
|
117
|
+
def primary(obj):
|
|
118
|
+
m = _meta_get(obj)
|
|
119
|
+
m["primary"] = True
|
|
120
|
+
return obj
|
|
121
|
+
|
|
122
|
+
def conditional(*, profiles: Tuple[str, ...] = (), require_env: Tuple[str, ...] = (), predicate: Optional[Callable[[], bool]] = None):
|
|
123
|
+
def dec(obj):
|
|
124
|
+
m = _meta_get(obj)
|
|
125
|
+
m["conditional"] = {"profiles": tuple(profiles), "require_env": tuple(require_env), "predicate": predicate}
|
|
126
|
+
return obj
|
|
127
|
+
return dec
|
|
128
|
+
|
|
129
|
+
def lazy(obj):
|
|
130
|
+
m = _meta_get(obj)
|
|
131
|
+
m["lazy"] = True
|
|
132
|
+
return obj
|
|
133
|
+
|
|
134
|
+
def configuration(cls=None, *, prefix: Optional[str] = None):
|
|
135
|
+
def dec(c):
|
|
136
|
+
setattr(c, PICO_INFRA, "configuration")
|
|
137
|
+
m = _meta_get(c)
|
|
138
|
+
if prefix is not None:
|
|
139
|
+
m["config_prefix"] = prefix
|
|
140
|
+
return c
|
|
141
|
+
return dec(cls) if cls else dec
|
|
142
|
+
|
|
143
|
+
def configure(fn):
|
|
144
|
+
m = _meta_get(fn)
|
|
145
|
+
m["configure"] = True
|
|
146
|
+
return fn
|
|
147
|
+
|
|
148
|
+
def cleanup(fn):
|
|
149
|
+
m = _meta_get(fn)
|
|
150
|
+
m["cleanup"] = True
|
|
151
|
+
return fn
|
|
152
|
+
|
|
153
|
+
def scope(name: str):
|
|
154
|
+
def dec(obj):
|
|
155
|
+
m = _meta_get(obj)
|
|
156
|
+
m["scope"] = name
|
|
157
|
+
return obj
|
|
158
|
+
return dec
|
|
159
|
+
|
|
160
|
+
def configured(target: Any, *, prefix: Optional[str] = None):
|
|
161
|
+
def dec(cls):
|
|
162
|
+
setattr(cls, PICO_INFRA, "configured")
|
|
163
|
+
m = _meta_get(cls)
|
|
164
|
+
m["configured"] = {"target": target, "prefix": prefix}
|
|
165
|
+
return cls
|
|
166
|
+
return dec
|
|
167
|
+
|
|
168
|
+
def _truthy(s: str) -> bool:
|
|
169
|
+
return s.strip().lower() in {"1", "true", "yes", "on", "y", "t"}
|
|
170
|
+
|
|
171
|
+
def _coerce(val: Optional[str], t: type) -> Any:
|
|
172
|
+
if val is None:
|
|
173
|
+
return None
|
|
174
|
+
if t is str:
|
|
175
|
+
return val
|
|
176
|
+
if t is int:
|
|
177
|
+
return int(val)
|
|
178
|
+
if t is float:
|
|
179
|
+
return float(val)
|
|
180
|
+
if t is bool:
|
|
181
|
+
return _truthy(val)
|
|
182
|
+
org = get_origin(t)
|
|
183
|
+
if org is Union:
|
|
184
|
+
args = [a for a in get_args(t) if a is not type(None)]
|
|
185
|
+
if not args:
|
|
186
|
+
return None
|
|
187
|
+
return _coerce(val, args[0])
|
|
188
|
+
return val
|
|
189
|
+
|
|
190
|
+
def _upper_key(name: str) -> str:
|
|
191
|
+
return name.upper()
|
|
192
|
+
|
|
193
|
+
def _lookup(sources: Tuple[ConfigSource, ...], key: str) -> Optional[str]:
|
|
194
|
+
for src in sources:
|
|
195
|
+
v = src.get(key)
|
|
196
|
+
if v is not None:
|
|
197
|
+
return v
|
|
198
|
+
return None
|
|
25
199
|
|
|
26
|
-
|
|
27
|
-
|
|
200
|
+
def _build_settings_instance(cls: type, sources: Tuple[ConfigSource, ...], prefix: Optional[str]) -> Any:
|
|
201
|
+
if not is_dataclass(cls):
|
|
202
|
+
raise ConfigurationError(f"Configuration class {getattr(cls, '__name__', str(cls))} must be a dataclass")
|
|
203
|
+
values: Dict[str, Any] = {}
|
|
204
|
+
for f in fields(cls):
|
|
205
|
+
base_key = _upper_key(f.name)
|
|
206
|
+
keys_to_try = []
|
|
207
|
+
if prefix:
|
|
208
|
+
keys_to_try.append(prefix + base_key)
|
|
209
|
+
keys_to_try.append(base_key)
|
|
210
|
+
raw = None
|
|
211
|
+
for k in keys_to_try:
|
|
212
|
+
raw = _lookup(sources, k)
|
|
213
|
+
if raw is not None:
|
|
214
|
+
break
|
|
215
|
+
if raw is None:
|
|
216
|
+
if f.default is not MISSING or f.default_factory is not MISSING:
|
|
217
|
+
continue
|
|
218
|
+
raise ConfigurationError(f"Missing configuration key: {(prefix or '') + base_key}")
|
|
219
|
+
values[f.name] = _coerce(raw, f.type if isinstance(f.type, type) or get_origin(f.type) else str)
|
|
220
|
+
return cls(**values)
|
|
221
|
+
|
|
222
|
+
def _extract_list_req(ann: Any):
|
|
223
|
+
def read_qualifier(metas: Iterable[Any]):
|
|
224
|
+
for m in metas:
|
|
225
|
+
if isinstance(m, Qualifier):
|
|
226
|
+
return str(m)
|
|
227
|
+
return None
|
|
228
|
+
origin = get_origin(ann)
|
|
229
|
+
if origin is Annotated:
|
|
230
|
+
args = get_args(ann)
|
|
231
|
+
base = args[0] if args else Any
|
|
232
|
+
metas = args[1:] if len(args) > 1 else ()
|
|
233
|
+
is_list, elem_t, qual = _extract_list_req(base)
|
|
234
|
+
if qual is None:
|
|
235
|
+
qual = read_qualifier(metas)
|
|
236
|
+
return is_list, elem_t, qual
|
|
237
|
+
if origin in (list, List):
|
|
238
|
+
elem = get_args(ann)[0] if get_args(ann) else Any
|
|
239
|
+
if get_origin(elem) is Annotated:
|
|
240
|
+
eargs = get_args(elem)
|
|
241
|
+
ebase = eargs[0] if eargs else Any
|
|
242
|
+
emetas = eargs[1:] if len(eargs) > 1 else ()
|
|
243
|
+
qual = read_qualifier(emetas)
|
|
244
|
+
return True, ebase if isinstance(ebase, type) else Any, qual
|
|
245
|
+
return True, elem if isinstance(elem, type) else Any, None
|
|
246
|
+
return False, None, None
|
|
247
|
+
|
|
248
|
+
def _implements_protocol(typ: type, proto: type) -> bool:
|
|
249
|
+
if not getattr(proto, "_is_protocol", False):
|
|
250
|
+
return False
|
|
28
251
|
try:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
fn_line = getattr(code, "co_firstlineno", None) if code else None
|
|
33
|
-
return (mod, qn, fn_line)
|
|
252
|
+
if getattr(proto, "__runtime_protocol__", False) or getattr(proto, "__annotations__", None) is not None:
|
|
253
|
+
inst = object.__new__(typ)
|
|
254
|
+
return isinstance(inst, proto)
|
|
34
255
|
except Exception:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
out = [(type(p).__module__, type(p).__qualname__) for p in plugins or ()]
|
|
39
|
-
return tuple(sorted(out))
|
|
40
|
-
|
|
41
|
-
def _normalize_for_fp(value):
|
|
42
|
-
if isinstance(value, ModuleType):
|
|
43
|
-
return getattr(value, "__name__", repr(value))
|
|
44
|
-
if isinstance(value, (tuple, list)):
|
|
45
|
-
return tuple(_normalize_for_fp(v) for v in value)
|
|
46
|
-
if isinstance(value, set):
|
|
47
|
-
return tuple(sorted(_normalize_for_fp(v) for v in value))
|
|
48
|
-
if callable(value):
|
|
49
|
-
return ("callable",) + _callable_id(value)
|
|
50
|
-
return value
|
|
51
|
-
|
|
52
|
-
_FP_EXCLUDE_KEYS = set()
|
|
53
|
-
|
|
54
|
-
def _normalize_overrides_for_fp(overrides: Optional[Dict[Any, Any]]) -> tuple:
|
|
55
|
-
if not overrides:
|
|
56
|
-
return ()
|
|
57
|
-
items = []
|
|
58
|
-
for k, v in overrides.items():
|
|
59
|
-
nk = _normalize_for_fp(k)
|
|
60
|
-
nv = _normalize_for_fp(v)
|
|
61
|
-
items.append((nk, nv))
|
|
62
|
-
return tuple(sorted(items))
|
|
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))
|
|
256
|
+
pass
|
|
257
|
+
for name, val in proto.__dict__.items():
|
|
258
|
+
if name.startswith("_") or not callable(val):
|
|
73
259
|
continue
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
def _collect_by_type(locator: ComponentLocator, t: type, q: Optional[str]):
|
|
263
|
+
keys = list(locator._metadata.keys())
|
|
264
|
+
out: List[KeyT] = []
|
|
265
|
+
for k in keys:
|
|
266
|
+
md = locator._metadata.get(k)
|
|
267
|
+
if md is None:
|
|
268
|
+
continue
|
|
269
|
+
typ = md.provided_type or md.concrete_class
|
|
270
|
+
if not isinstance(typ, type):
|
|
271
|
+
continue
|
|
272
|
+
ok = False
|
|
273
|
+
try:
|
|
274
|
+
ok = issubclass(typ, t)
|
|
275
|
+
except Exception:
|
|
276
|
+
ok = _implements_protocol(typ, t)
|
|
277
|
+
if ok and (q is None or q in md.qualifiers):
|
|
278
|
+
out.append(k)
|
|
279
|
+
return out
|
|
280
|
+
|
|
281
|
+
def _resolve_args(callable_obj: Callable[..., Any], pico: "PicoContainer") -> Dict[str, Any]:
|
|
282
|
+
sig = inspect.signature(callable_obj)
|
|
283
|
+
kwargs: Dict[str, Any] = {}
|
|
284
|
+
for name, param in sig.parameters.items():
|
|
285
|
+
if name in ("self", "cls"):
|
|
286
|
+
continue
|
|
287
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
288
|
+
continue
|
|
289
|
+
ann = param.annotation
|
|
290
|
+
is_list, elem_t, qual = _extract_list_req(ann)
|
|
291
|
+
if is_list and pico._locator is not None and isinstance(elem_t, type):
|
|
292
|
+
keys = _collect_by_type(pico._locator, elem_t, qual)
|
|
293
|
+
kwargs[name] = [pico.get(k) for k in keys]
|
|
294
|
+
continue
|
|
295
|
+
if ann is not inspect._empty and isinstance(ann, type):
|
|
296
|
+
key: KeyT = ann
|
|
297
|
+
elif ann is not inspect._empty and isinstance(ann, str):
|
|
298
|
+
key = ann
|
|
299
|
+
else:
|
|
300
|
+
key = name
|
|
301
|
+
kwargs[name] = pico.get(key)
|
|
302
|
+
return kwargs
|
|
303
|
+
|
|
304
|
+
def _needs_async_configure(obj: Any) -> bool:
|
|
305
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
306
|
+
meta = getattr(m, PICO_META, {})
|
|
307
|
+
if meta.get("configure", False) and inspect.iscoroutinefunction(m):
|
|
308
|
+
return True
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
def _iter_configure_methods(obj: Any):
|
|
312
|
+
for _, m in inspect.getmembers(obj, predicate=inspect.ismethod):
|
|
313
|
+
meta = getattr(m, PICO_META, {})
|
|
314
|
+
if meta.get("configure", False):
|
|
315
|
+
yield m
|
|
316
|
+
|
|
317
|
+
def _build_class(cls: type, pico: "PicoContainer", locator: ComponentLocator) -> Any:
|
|
318
|
+
init = cls.__init__
|
|
319
|
+
if init is object.__init__:
|
|
320
|
+
inst = cls()
|
|
321
|
+
else:
|
|
322
|
+
deps = _resolve_args(init, pico)
|
|
323
|
+
inst = cls(**deps)
|
|
324
|
+
ainit = getattr(inst, "__ainit__", None)
|
|
325
|
+
has_async = (callable(ainit) and inspect.iscoroutinefunction(ainit)) or _needs_async_configure(inst)
|
|
326
|
+
if has_async:
|
|
327
|
+
async def runner():
|
|
328
|
+
if callable(ainit):
|
|
329
|
+
kwargs = {}
|
|
87
330
|
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__))
|
|
331
|
+
kwargs = _resolve_args(ainit, pico)
|
|
94
332
|
except Exception:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
333
|
+
kwargs = {}
|
|
334
|
+
res = ainit(**kwargs)
|
|
335
|
+
if inspect.isawaitable(res):
|
|
336
|
+
await res
|
|
337
|
+
for m in _iter_configure_methods(inst):
|
|
338
|
+
args = _resolve_args(m, pico)
|
|
339
|
+
r = m(**args)
|
|
340
|
+
if inspect.isawaitable(r):
|
|
341
|
+
await r
|
|
342
|
+
return inst
|
|
343
|
+
return runner()
|
|
344
|
+
for m in _iter_configure_methods(inst):
|
|
345
|
+
args = _resolve_args(m, pico)
|
|
346
|
+
m(**args)
|
|
347
|
+
return inst
|
|
348
|
+
|
|
349
|
+
def _build_method(fn: Callable[..., Any], pico: "PicoContainer", locator: ComponentLocator) -> Any:
|
|
350
|
+
deps = _resolve_args(fn, pico)
|
|
351
|
+
obj = fn(**deps)
|
|
352
|
+
has_async = _needs_async_configure(obj)
|
|
353
|
+
if has_async:
|
|
354
|
+
async def runner():
|
|
355
|
+
for m in _iter_configure_methods(obj):
|
|
356
|
+
args = _resolve_args(m, pico)
|
|
357
|
+
r = m(**args)
|
|
358
|
+
if inspect.isawaitable(r):
|
|
359
|
+
await r
|
|
360
|
+
return obj
|
|
361
|
+
return runner()
|
|
362
|
+
for m in _iter_configure_methods(obj):
|
|
363
|
+
args = _resolve_args(m, pico)
|
|
364
|
+
m(**args)
|
|
365
|
+
return obj
|
|
366
|
+
|
|
367
|
+
def _get_return_type(fn: Callable[..., Any]) -> Optional[type]:
|
|
122
368
|
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)
|
|
369
|
+
ra = inspect.signature(fn).return_annotation
|
|
128
370
|
except Exception:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
) ->
|
|
139
|
-
if
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
371
|
+
return None
|
|
372
|
+
if ra is inspect._empty:
|
|
373
|
+
return None
|
|
374
|
+
return ra if isinstance(ra, type) else None
|
|
375
|
+
|
|
376
|
+
def _scan_package(package) -> Iterable[Any]:
|
|
377
|
+
for _, name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
|
|
378
|
+
yield importlib.import_module(name)
|
|
379
|
+
|
|
380
|
+
def _iter_input_modules(inputs: Union[Any, Iterable[Any]]) -> Iterable[Any]:
|
|
381
|
+
seq = inputs if isinstance(inputs, Iterable) and not inspect.ismodule(inputs) and not isinstance(inputs, str) else [inputs]
|
|
382
|
+
seen: Set[str] = set()
|
|
383
|
+
for it in seq:
|
|
384
|
+
if isinstance(it, str):
|
|
385
|
+
mod = importlib.import_module(it)
|
|
386
|
+
else:
|
|
387
|
+
mod = it
|
|
388
|
+
if hasattr(mod, "__path__"):
|
|
389
|
+
for sub in _scan_package(mod):
|
|
390
|
+
name = getattr(sub, "__name__", None)
|
|
391
|
+
if name and name not in seen:
|
|
392
|
+
seen.add(name)
|
|
393
|
+
yield sub
|
|
394
|
+
else:
|
|
395
|
+
name = getattr(mod, "__name__", None)
|
|
396
|
+
if name and name not in seen:
|
|
397
|
+
seen.add(name)
|
|
398
|
+
yield mod
|
|
399
|
+
|
|
400
|
+
def _can_be_selected_for(reg_md: Dict[KeyT, ProviderMetadata], selector: Any) -> bool:
|
|
401
|
+
if not isinstance(selector, type):
|
|
402
|
+
return False
|
|
403
|
+
for md in reg_md.values():
|
|
404
|
+
typ = md.provided_type or md.concrete_class
|
|
405
|
+
if isinstance(typ, type):
|
|
160
406
|
try:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def
|
|
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
|
-
|
|
407
|
+
if issubclass(typ, selector):
|
|
408
|
+
return True
|
|
409
|
+
except Exception:
|
|
410
|
+
continue
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
def _normalize_override_provider(v: Any) -> Tuple[Provider, bool]:
|
|
414
|
+
if isinstance(v, tuple) and len(v) == 2:
|
|
415
|
+
src, lz = v
|
|
416
|
+
if callable(src):
|
|
417
|
+
return (lambda s=src: s()), bool(lz)
|
|
418
|
+
return (lambda s=src: s), bool(lz)
|
|
419
|
+
if callable(v):
|
|
420
|
+
return (lambda f=v: f()), False
|
|
421
|
+
return (lambda inst=v: inst), False
|
|
422
|
+
|
|
423
|
+
class Registrar:
|
|
424
|
+
def __init__(
|
|
425
|
+
self,
|
|
426
|
+
factory: ComponentFactory,
|
|
427
|
+
*,
|
|
428
|
+
profiles: Tuple[str, ...] = (),
|
|
429
|
+
environ: Optional[Dict[str, str]] = None,
|
|
430
|
+
logger: Optional[logging.Logger] = None,
|
|
431
|
+
config: Tuple[ConfigSource, ...] = (),
|
|
432
|
+
tree_sources: Tuple["TreeSource", ...] = ()
|
|
433
|
+
) -> None:
|
|
434
|
+
self._factory = factory
|
|
435
|
+
self._profiles = set(p.strip() for p in profiles if p)
|
|
436
|
+
self._environ = environ if environ is not None else os.environ
|
|
437
|
+
self._deferred: List[DeferredProvider] = []
|
|
438
|
+
self._candidates: Dict[KeyT, List[Tuple[bool, Provider, ProviderMetadata]]] = {}
|
|
439
|
+
self._metadata: Dict[KeyT, ProviderMetadata] = {}
|
|
440
|
+
self._indexes: Dict[str, Dict[Any, List[KeyT]]] = {}
|
|
441
|
+
self._on_missing: List[Tuple[int, KeyT, type]] = []
|
|
442
|
+
self._log = logger or LOGGER
|
|
443
|
+
self._config_sources: Tuple[ConfigSource, ...] = tuple(config)
|
|
444
|
+
from .config_runtime import ConfigResolver, TypeAdapterRegistry, ObjectGraphBuilder
|
|
445
|
+
self._resolver = ConfigResolver(tuple(tree_sources))
|
|
446
|
+
self._adapters = TypeAdapterRegistry()
|
|
447
|
+
self._graph = ObjectGraphBuilder(self._resolver, self._adapters)
|
|
448
|
+
|
|
449
|
+
def locator(self) -> ComponentLocator:
|
|
450
|
+
return ComponentLocator(self._metadata, self._indexes)
|
|
451
|
+
|
|
452
|
+
def attach_runtime(self, pico, locator: ComponentLocator) -> None:
|
|
453
|
+
for deferred in self._deferred:
|
|
454
|
+
deferred.attach(pico, locator)
|
|
455
|
+
for key, md in list(self._metadata.items()):
|
|
456
|
+
if md.lazy:
|
|
457
|
+
original = self._factory.get(key)
|
|
458
|
+
def lazy_proxy_provider(_orig=original, _p=pico):
|
|
459
|
+
return UnifiedComponentProxy(container=_p, object_creator=_orig)
|
|
460
|
+
self._factory.bind(key, lazy_proxy_provider)
|
|
461
|
+
|
|
462
|
+
def _queue(self, key: KeyT, provider: Provider, md: ProviderMetadata) -> None:
|
|
463
|
+
lst = self._candidates.setdefault(key, [])
|
|
464
|
+
lst.append((md.primary, provider, md))
|
|
465
|
+
if isinstance(provider, DeferredProvider):
|
|
466
|
+
self._deferred.append(provider)
|
|
467
|
+
|
|
468
|
+
def _bind_if_absent(self, key: KeyT, provider: Provider) -> None:
|
|
469
|
+
if not self._factory.has(key):
|
|
470
|
+
self._factory.bind(key, provider)
|
|
471
|
+
|
|
472
|
+
def _enabled_by_condition(self, obj: Any) -> bool:
|
|
473
|
+
meta = getattr(obj, PICO_META, {})
|
|
474
|
+
c = meta.get("conditional", None)
|
|
475
|
+
if not c:
|
|
476
|
+
return True
|
|
477
|
+
p = set(c.get("profiles") or ())
|
|
478
|
+
if p and not (p & self._profiles):
|
|
479
|
+
self._log.info("excluded_by_profile name=%s need=%s active=%s", getattr(obj, "__name__", str(obj)), sorted(p), sorted(self._profiles))
|
|
480
|
+
return False
|
|
481
|
+
req = c.get("require_env") or ()
|
|
482
|
+
for k in req:
|
|
483
|
+
if k not in self._environ or not self._environ.get(k):
|
|
484
|
+
self._log.info("excluded_by_env name=%s env=%s", getattr(obj, "__name__", str(obj)), k)
|
|
485
|
+
return False
|
|
486
|
+
pred = c.get("predicate")
|
|
487
|
+
if pred is None:
|
|
488
|
+
return True
|
|
489
|
+
try:
|
|
490
|
+
ok = bool(pred())
|
|
491
|
+
except Exception as e:
|
|
492
|
+
self._log.info("excluded_by_predicate_error name=%s error=%s", getattr(obj, "__name__", str(obj)), repr(e))
|
|
493
|
+
return False
|
|
494
|
+
if not ok:
|
|
495
|
+
self._log.info("excluded_by_predicate name=%s", getattr(obj, "__name__", str(obj)))
|
|
496
|
+
return ok
|
|
497
|
+
|
|
498
|
+
def _register_component_class(self, cls: type) -> None:
|
|
499
|
+
if not self._enabled_by_condition(cls):
|
|
500
|
+
return
|
|
501
|
+
key = getattr(cls, PICO_KEY, cls)
|
|
502
|
+
provider = DeferredProvider(lambda pico, loc, c=cls: _build_class(c, pico, loc))
|
|
503
|
+
qset = set(str(q) for q in getattr(cls, PICO_META, {}).get("qualifier", ()))
|
|
504
|
+
sc = getattr(cls, PICO_META, {}).get("scope", "singleton")
|
|
505
|
+
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)
|
|
506
|
+
self._queue(key, provider, md)
|
|
507
|
+
|
|
508
|
+
def _register_factory_class(self, cls: type) -> None:
|
|
509
|
+
if not self._enabled_by_condition(cls):
|
|
510
|
+
return
|
|
511
|
+
for name in dir(cls):
|
|
207
512
|
try:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
513
|
+
real = getattr(cls, name)
|
|
514
|
+
except Exception:
|
|
515
|
+
continue
|
|
516
|
+
if callable(real) and getattr(real, PICO_INFRA, None) == "provides":
|
|
517
|
+
if not self._enabled_by_condition(real):
|
|
518
|
+
continue
|
|
519
|
+
k = getattr(real, PICO_KEY)
|
|
520
|
+
provider = DeferredProvider(lambda pico, loc, fc=cls, mn=name: _build_method(getattr(_build_class(fc, pico, loc), mn), pico, loc))
|
|
521
|
+
rt = _get_return_type(real)
|
|
522
|
+
qset = set(str(q) for q in getattr(real, PICO_META, {}).get("qualifier", ()))
|
|
523
|
+
sc = getattr(real, PICO_META, {}).get("scope", getattr(cls, PICO_META, {}).get("scope", "singleton"))
|
|
524
|
+
md = ProviderMetadata(key=k, provided_type=rt if isinstance(rt, type) else (k if isinstance(k, type) else None), concrete_class=None, factory_class=cls, factory_method=name, qualifiers=qset, primary=bool(getattr(real, PICO_META, {}).get("primary")), lazy=bool(getattr(real, PICO_META, {}).get("lazy", False)), infra=getattr(cls, PICO_INFRA, None), pico_name=getattr(real, PICO_NAME, None), scope=sc)
|
|
525
|
+
self._queue(k, provider, md)
|
|
526
|
+
|
|
527
|
+
def _register_configuration_class(self, cls: type) -> None:
|
|
528
|
+
if not self._enabled_by_condition(cls):
|
|
529
|
+
return
|
|
530
|
+
pref = getattr(cls, PICO_META, {}).get("config_prefix", None)
|
|
531
|
+
if is_dataclass(cls):
|
|
532
|
+
key = cls
|
|
533
|
+
provider = DeferredProvider(lambda pico, loc, c=cls, p=pref, src=self._config_sources: _build_settings_instance(c, src, p))
|
|
534
|
+
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")
|
|
535
|
+
self._queue(key, provider, md)
|
|
536
|
+
|
|
537
|
+
def _register_configured_class(self, cls: type) -> None:
|
|
538
|
+
if not self._enabled_by_condition(cls):
|
|
539
|
+
return
|
|
540
|
+
meta = getattr(cls, PICO_META, {})
|
|
541
|
+
cfg = meta.get("configured", None)
|
|
542
|
+
if not cfg:
|
|
543
|
+
return
|
|
544
|
+
target = cfg.get("target")
|
|
545
|
+
prefix = cfg.get("prefix")
|
|
546
|
+
if not isinstance(target, type):
|
|
547
|
+
return
|
|
548
|
+
provider = DeferredProvider(lambda pico, loc, t=target, p=prefix, g=self._graph: g.build_from_prefix(t, p))
|
|
549
|
+
qset = set(str(q) for q in meta.get("qualifier", ()))
|
|
550
|
+
sc = meta.get("scope", "singleton")
|
|
551
|
+
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)
|
|
552
|
+
self._queue(target, provider, md)
|
|
553
|
+
|
|
554
|
+
def register_module(self, module: Any) -> None:
|
|
555
|
+
for _, obj in inspect.getmembers(module):
|
|
556
|
+
if inspect.isclass(obj):
|
|
557
|
+
meta = getattr(obj, PICO_META, {})
|
|
558
|
+
if "on_missing" in meta:
|
|
559
|
+
sel = meta["on_missing"]["selector"]
|
|
560
|
+
pr = int(meta["on_missing"].get("priority", 0))
|
|
561
|
+
self._on_missing.append((pr, sel, obj))
|
|
562
|
+
continue
|
|
563
|
+
infra = getattr(obj, PICO_INFRA, None)
|
|
564
|
+
if infra == "component":
|
|
565
|
+
self._register_component_class(obj)
|
|
566
|
+
elif infra == "factory":
|
|
567
|
+
self._register_factory_class(obj)
|
|
568
|
+
elif infra == "configuration":
|
|
569
|
+
self._register_configuration_class(obj)
|
|
570
|
+
elif infra == "configured":
|
|
571
|
+
self._register_configured_class(obj)
|
|
572
|
+
|
|
573
|
+
def _prefix_exists(self, md: ProviderMetadata) -> bool:
|
|
574
|
+
if md.infra != "configured":
|
|
575
|
+
return False
|
|
576
|
+
try:
|
|
577
|
+
_ = self._resolver.subtree(md.pico_name)
|
|
578
|
+
return True
|
|
579
|
+
except Exception:
|
|
580
|
+
return False
|
|
581
|
+
|
|
582
|
+
def select_and_bind(self) -> None:
|
|
583
|
+
for key, lst in self._candidates.items():
|
|
584
|
+
def rank(item: Tuple[bool, Provider, ProviderMetadata]) -> Tuple[int, int, int]:
|
|
585
|
+
is_present = 1 if self._prefix_exists(item[2]) else 0
|
|
586
|
+
pref = str(item[2].pico_name or "")
|
|
587
|
+
pref_len = len(pref)
|
|
588
|
+
is_primary = 1 if item[0] else 0
|
|
589
|
+
return (is_present, pref_len, is_primary)
|
|
590
|
+
lst_sorted = sorted(lst, key=rank, reverse=True)
|
|
591
|
+
chosen = lst_sorted[0]
|
|
592
|
+
self._bind_if_absent(key, chosen[1])
|
|
593
|
+
self._metadata[key] = chosen[2]
|
|
594
|
+
|
|
595
|
+
def _find_md_for_type(self, t: type) -> Optional[ProviderMetadata]:
|
|
596
|
+
cands: List[ProviderMetadata] = []
|
|
597
|
+
for md in self._metadata.values():
|
|
598
|
+
typ = md.provided_type or md.concrete_class
|
|
599
|
+
if not isinstance(typ, type):
|
|
600
|
+
continue
|
|
601
|
+
try:
|
|
602
|
+
if issubclass(typ, t):
|
|
603
|
+
cands.append(md)
|
|
604
|
+
except Exception:
|
|
605
|
+
continue
|
|
606
|
+
if not cands:
|
|
607
|
+
return None
|
|
608
|
+
prim = [m for m in cands if m.primary]
|
|
609
|
+
return prim[0] if prim else cands[0]
|
|
610
|
+
|
|
611
|
+
def _iter_param_types(self, callable_obj: Callable[..., Any]) -> Iterable[type]:
|
|
612
|
+
sig = inspect.signature(callable_obj)
|
|
613
|
+
for name, param in sig.parameters.items():
|
|
614
|
+
if name in ("self", "cls"):
|
|
615
|
+
continue
|
|
616
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
617
|
+
continue
|
|
618
|
+
ann = param.annotation
|
|
619
|
+
is_list, elem_t, _ = _extract_list_req(ann)
|
|
620
|
+
t = elem_t if is_list else (ann if isinstance(ann, type) else None)
|
|
621
|
+
if isinstance(t, type):
|
|
622
|
+
yield t
|
|
623
|
+
|
|
624
|
+
def _infer_narrower_scope(self, md: ProviderMetadata) -> Optional[str]:
|
|
625
|
+
if md.concrete_class is not None:
|
|
626
|
+
init = md.concrete_class.__init__
|
|
627
|
+
for t in self._iter_param_types(init):
|
|
628
|
+
dep = self._find_md_for_type(t)
|
|
629
|
+
if dep and dep.scope != "singleton":
|
|
630
|
+
return dep.scope
|
|
631
|
+
if md.factory_class is not None and md.factory_method is not None:
|
|
632
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
633
|
+
for t in self._iter_param_types(fn):
|
|
634
|
+
dep = self._find_md_for_type(t)
|
|
635
|
+
if dep and dep.scope != "singleton":
|
|
636
|
+
return dep.scope
|
|
637
|
+
return None
|
|
638
|
+
|
|
639
|
+
def _promote_scopes(self) -> None:
|
|
640
|
+
for k, md in list(self._metadata.items()):
|
|
641
|
+
if md.scope == "singleton":
|
|
642
|
+
ns = self._infer_narrower_scope(md)
|
|
643
|
+
if ns and ns != "singleton":
|
|
644
|
+
self._metadata[k] = ProviderMetadata(
|
|
645
|
+
key=md.key,
|
|
646
|
+
provided_type=md.provided_type,
|
|
647
|
+
concrete_class=md.concrete_class,
|
|
648
|
+
factory_class=md.factory_class,
|
|
649
|
+
factory_method=md.factory_method,
|
|
650
|
+
qualifiers=md.qualifiers,
|
|
651
|
+
primary=md.primary,
|
|
652
|
+
lazy=md.lazy,
|
|
653
|
+
infra=md.infra,
|
|
654
|
+
pico_name=md.pico_name,
|
|
655
|
+
override=md.override,
|
|
656
|
+
scope=ns
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
def _rebuild_indexes(self) -> None:
|
|
660
|
+
self._indexes.clear()
|
|
661
|
+
def add(idx: str, val: Any, key: KeyT):
|
|
662
|
+
b = self._indexes.setdefault(idx, {}).setdefault(val, [])
|
|
663
|
+
if key not in b:
|
|
664
|
+
b.append(key)
|
|
665
|
+
for k, md in self._metadata.items():
|
|
666
|
+
for q in md.qualifiers:
|
|
667
|
+
add("qualifier", q, k)
|
|
668
|
+
if md.primary:
|
|
669
|
+
add("primary", True, k)
|
|
670
|
+
add("lazy", bool(md.lazy), k)
|
|
671
|
+
if md.infra is not None:
|
|
672
|
+
add("infra", md.infra, k)
|
|
673
|
+
if md.pico_name is not None:
|
|
674
|
+
add("pico_name", md.pico_name, k)
|
|
675
|
+
|
|
676
|
+
def _find_md_for_name(self, name: str) -> Optional[KeyT]:
|
|
677
|
+
for k, md in self._metadata.items():
|
|
678
|
+
if md.pico_name == name:
|
|
679
|
+
return k
|
|
680
|
+
t = md.provided_type or md.concrete_class
|
|
681
|
+
if isinstance(t, type) and getattr(t, "__name__", "") == name:
|
|
682
|
+
return k
|
|
683
|
+
return None
|
|
684
|
+
|
|
685
|
+
def _validate_bindings(self) -> None:
|
|
686
|
+
errors: List[str] = []
|
|
687
|
+
def _skip_type(t: type) -> bool:
|
|
688
|
+
if t in (str, int, float, bool, bytes):
|
|
689
|
+
return True
|
|
690
|
+
if t is Any:
|
|
691
|
+
return True
|
|
692
|
+
if getattr(t, "_is_protocol", False):
|
|
693
|
+
return True
|
|
694
|
+
return False
|
|
695
|
+
def _should_validate(param: inspect.Parameter) -> bool:
|
|
696
|
+
if param.default is not inspect._empty:
|
|
697
|
+
return False
|
|
698
|
+
ann = param.annotation
|
|
699
|
+
origin = get_origin(ann)
|
|
700
|
+
if origin is Union:
|
|
701
|
+
args = get_args(ann)
|
|
702
|
+
if type(None) in args:
|
|
703
|
+
return False
|
|
704
|
+
return True
|
|
705
|
+
loc = ComponentLocator(self._metadata, self._indexes)
|
|
706
|
+
for k, md in self._metadata.items():
|
|
707
|
+
if md.infra == "configuration":
|
|
708
|
+
continue
|
|
709
|
+
callables_to_check: List[Callable[..., Any]] = []
|
|
710
|
+
if md.concrete_class is not None:
|
|
711
|
+
callables_to_check.append(md.concrete_class.__init__)
|
|
712
|
+
if md.factory_class is not None and md.factory_method is not None:
|
|
713
|
+
fn = getattr(md.factory_class, md.factory_method)
|
|
714
|
+
callables_to_check.append(fn)
|
|
715
|
+
for callable_obj in callables_to_check:
|
|
716
|
+
sig = inspect.signature(callable_obj)
|
|
717
|
+
for name, param in sig.parameters.items():
|
|
718
|
+
if name in ("self", "cls"):
|
|
719
|
+
continue
|
|
720
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
721
|
+
continue
|
|
722
|
+
if not _should_validate(param):
|
|
723
|
+
continue
|
|
724
|
+
ann = param.annotation
|
|
725
|
+
is_list, elem_t, qual = _extract_list_req(ann)
|
|
726
|
+
if is_list:
|
|
727
|
+
if qual:
|
|
728
|
+
matching = loc.with_qualifier_any(qual).keys()
|
|
729
|
+
if not matching:
|
|
730
|
+
errors.append(f"{getattr(k,'__name__',k)} expects List[{getattr(elem_t,'__name__',elem_t)}] with qualifier '{qual}' but no matching components exist")
|
|
731
|
+
continue
|
|
732
|
+
if isinstance(ann, str):
|
|
733
|
+
if ann in self._metadata or self._factory.has(ann) or self._find_md_for_name(ann) is not None:
|
|
734
|
+
continue
|
|
735
|
+
errors.append(f"{getattr(k,'__name__',k)} depends on string key '{ann}' which is not bound")
|
|
736
|
+
continue
|
|
737
|
+
if isinstance(ann, type) and not _skip_type(ann):
|
|
738
|
+
dep = self._find_md_for_type(ann)
|
|
739
|
+
if dep is None:
|
|
740
|
+
loc_name = "constructor" if callable_obj.__name__ == "__init__" else f"factory {md.factory_class.__name__}.{md.factory_method}"
|
|
741
|
+
errors.append(f"{getattr(k,'__name__',k)} {loc_name} depends on {getattr(ann,'__name__',ann)} which is not bound")
|
|
742
|
+
if errors:
|
|
743
|
+
raise InvalidBindingError(errors)
|
|
744
|
+
|
|
745
|
+
def finalize(self, overrides: Optional[Dict[KeyT, Any]]) -> None:
|
|
746
|
+
self.select_and_bind()
|
|
747
|
+
self._promote_scopes()
|
|
748
|
+
self._rebuild_indexes()
|
|
749
|
+
for _, selector, default_cls in sorted(self._on_missing, key=lambda x: -x[0]):
|
|
750
|
+
key = selector
|
|
751
|
+
if key in self._metadata or self._factory.has(key) or _can_be_selected_for(self._metadata, selector):
|
|
752
|
+
continue
|
|
753
|
+
provider = DeferredProvider(lambda pico, loc, c=default_cls: _build_class(c, pico, loc))
|
|
754
|
+
qset = set(str(q) for q in getattr(default_cls, PICO_META, {}).get("qualifier", ()))
|
|
755
|
+
sc = getattr(default_cls, PICO_META, {}).get("scope", "singleton")
|
|
756
|
+
md = ProviderMetadata(
|
|
757
|
+
key=key,
|
|
758
|
+
provided_type=key if isinstance(key, type) else None,
|
|
759
|
+
concrete_class=default_cls,
|
|
760
|
+
factory_class=None,
|
|
761
|
+
factory_method=None,
|
|
762
|
+
qualifiers=qset,
|
|
763
|
+
primary=True,
|
|
764
|
+
lazy=bool(getattr(default_cls, PICO_META, {}).get("lazy", False)),
|
|
765
|
+
infra=getattr(default_cls, PICO_INFRA, None),
|
|
766
|
+
pico_name=getattr(default_cls, PICO_NAME, None),
|
|
767
|
+
override=True,
|
|
768
|
+
scope=sc
|
|
769
|
+
)
|
|
770
|
+
self._bind_if_absent(key, provider)
|
|
771
|
+
self._metadata[key] = md
|
|
772
|
+
if isinstance(provider, DeferredProvider):
|
|
773
|
+
self._deferred.append(provider)
|
|
774
|
+
self._rebuild_indexes()
|
|
775
|
+
self._validate_bindings()
|
|
776
|
+
|
|
777
|
+
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", ...] = ()) -> PicoContainer:
|
|
778
|
+
active = tuple(p.strip() for p in profiles if p)
|
|
779
|
+
allowed_set = set(a.strip() for a in allowed_profiles) if allowed_profiles is not None else None
|
|
780
|
+
if allowed_set is not None:
|
|
781
|
+
unknown = set(active) - allowed_set
|
|
782
|
+
if unknown:
|
|
783
|
+
raise ConfigurationError(f"Unknown profiles: {sorted(unknown)}; allowed: {sorted(allowed_set)}")
|
|
784
|
+
factory = ComponentFactory()
|
|
785
|
+
caches = ScopedCaches()
|
|
786
|
+
scopes = ScopeManager()
|
|
787
|
+
if custom_scopes:
|
|
788
|
+
for n, impl in custom_scopes.items():
|
|
789
|
+
scopes.register_scope(n, impl)
|
|
790
|
+
pico = PicoContainer(factory, caches, scopes, container_id=container_id, profiles=active)
|
|
791
|
+
registrar = Registrar(factory, profiles=active, environ=environ, logger=logger, config=config, tree_sources=tree_config)
|
|
792
|
+
for m in _iter_input_modules(modules):
|
|
793
|
+
registrar.register_module(m)
|
|
794
|
+
if overrides:
|
|
795
|
+
for k, v in overrides.items():
|
|
796
|
+
prov, _ = _normalize_override_provider(v)
|
|
797
|
+
factory.bind(k, prov)
|
|
798
|
+
registrar.finalize(overrides)
|
|
799
|
+
if validate_only:
|
|
800
|
+
return pico
|
|
801
|
+
locator = registrar.locator()
|
|
802
|
+
registrar.attach_runtime(pico, locator)
|
|
803
|
+
pico.attach_locator(locator)
|
|
804
|
+
return pico
|
|
222
805
|
|