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/interceptors.py DELETED
@@ -1,76 +0,0 @@
1
- from __future__ import annotations
2
- import inspect
3
- from typing import Any, Callable, Protocol, Sequence
4
-
5
- class MethodCtx:
6
- __slots__ = ("instance", "cls", "method", "name", "args", "kwargs", "container", "tags", "qualifiers", "request_key", "local")
7
- def __init__(self, *, instance: object, cls: type, method: Callable[..., Any], name: str, args: tuple, kwargs: dict, container: Any, tags: set[str] | None = None, qualifiers: dict[str, Any] | None = None, request_key: Any = None):
8
- self.instance = instance
9
- self.cls = cls
10
- self.method = method
11
- self.name = name
12
- self.args = args
13
- self.kwargs = kwargs
14
- self.container = container
15
- self.tags = set(tags or ())
16
- self.qualifiers = dict(qualifiers or {})
17
- self.request_key = request_key
18
- self.local: dict[str, Any] = {}
19
-
20
- class ResolveCtx:
21
- __slots__ = ("key", "qualifiers", "requested_by", "profiles", "local")
22
- def __init__(self, *, key: Any, qualifiers: dict[str, Any] | None, requested_by: Any, profiles: Sequence[str]):
23
- self.key = key
24
- self.qualifiers = dict(qualifiers or {})
25
- self.requested_by = requested_by
26
- self.profiles = tuple(profiles or ())
27
- self.local: dict[str, Any] = {}
28
-
29
- class CreateCtx:
30
- __slots__ = ("key", "component", "provider", "profiles", "local")
31
- def __init__(self, *, key: Any, component: type | None, provider: Callable[[], object], profiles: Sequence[str]):
32
- self.key = key
33
- self.component = component
34
- self.provider = provider
35
- self.profiles = tuple(profiles or ())
36
- self.local: dict[str, Any] = {}
37
-
38
- class MethodInterceptor(Protocol):
39
- def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any: ...
40
-
41
- class ContainerInterceptor(Protocol):
42
- def around_resolve(self, ctx: ResolveCtx, call_next: Callable[[ResolveCtx], Any]) -> Any: ...
43
- def around_create(self, ctx: CreateCtx, call_next: Callable[[CreateCtx], Any]) -> Any: ...
44
-
45
- def _dispatch_method(interceptors: Sequence[MethodInterceptor], ctx: MethodCtx, i: int = 0):
46
- if i >= len(interceptors):
47
- return ctx.method(*ctx.args, **ctx.kwargs)
48
- cur = interceptors[i]
49
- return cur.invoke(ctx, lambda nxt: _dispatch_method(interceptors, nxt, i + 1))
50
-
51
- async def _dispatch_method_async(interceptors: Sequence[MethodInterceptor], ctx: MethodCtx, i: int = 0):
52
- if i >= len(interceptors):
53
- return await ctx.method(*ctx.args, **ctx.kwargs)
54
- cur = interceptors[i]
55
- res = cur.invoke(ctx, lambda nxt: _dispatch_method_async(interceptors, nxt, i + 1))
56
- return await res if inspect.isawaitable(res) else res
57
-
58
- def dispatch_method(interceptors: Sequence[MethodInterceptor], ctx: MethodCtx):
59
- if inspect.iscoroutinefunction(ctx.method):
60
- return _dispatch_method_async(interceptors, ctx, 0)
61
- return _dispatch_method(interceptors, ctx, 0)
62
-
63
- def run_resolve_chain(interceptors: Sequence[ContainerInterceptor], ctx: ResolveCtx):
64
- def call(i: int, c: ResolveCtx):
65
- if i >= len(interceptors):
66
- return None
67
- return interceptors[i].around_resolve(c, lambda nxt: call(i + 1, nxt))
68
- return call(0, ctx)
69
-
70
- def run_create_chain(interceptors: Sequence[ContainerInterceptor], ctx: CreateCtx):
71
- def call(i: int, c: CreateCtx):
72
- if i >= len(interceptors):
73
- return c.provider()
74
- return interceptors[i].around_create(c, lambda nxt: call(i + 1, nxt))
75
- return call(0, ctx)
76
-
pico_ioc/plugins.py DELETED
@@ -1,28 +0,0 @@
1
- # pico_ioc/plugins.py
2
- from typing import Protocol, Any, Tuple
3
- from .container import Binder, PicoContainer
4
- import logging
5
-
6
- class PicoPlugin(Protocol):
7
- def before_scan(self, package: Any, binder: Binder) -> None: ...
8
- def visit_class(self, module: Any, cls: type, binder: Binder) -> None: ...
9
- def after_scan(self, package: Any, binder: Binder) -> None: ...
10
- def after_bind(self, container: PicoContainer, binder: Binder) -> None: ...
11
- def before_eager(self, container: PicoContainer, binder: Binder) -> None: ...
12
- def after_ready(self, container: PicoContainer, binder: Binder) -> None: ...
13
-
14
- def run_plugin_hook(
15
- plugins: Tuple[PicoPlugin, ...],
16
- hook_name: str,
17
- *args,
18
- **kwargs,
19
- ) -> None:
20
- """Run a lifecycle hook across all plugins, logging (but not raising) exceptions."""
21
- for pl in plugins:
22
- try:
23
- fn = getattr(pl, hook_name, None)
24
- if fn:
25
- fn(*args, **kwargs)
26
- except Exception:
27
- logging.exception("Plugin %s failed", hook_name)
28
-
pico_ioc/policy.py DELETED
@@ -1,245 +0,0 @@
1
- # src/pico_ioc/policy.py
2
- from __future__ import annotations
3
-
4
- import inspect
5
- import os
6
- from collections import defaultdict
7
- from typing import Any, Dict, List, Optional, Tuple
8
-
9
- from .utils import create_alias_provider
10
- from .decorators import CONDITIONAL_META, PRIMARY_FLAG, ON_MISSING_META
11
- from . import _state
12
-
13
-
14
- # ------------------- helpers -------------------
15
-
16
- def _target_from_provider(provider):
17
- """Try to resolve the 'real' target behind a provider closure (class, function or bound method)."""
18
- fn = provider
19
- try:
20
- cells = getattr(fn, "__closure__", None) or ()
21
- first_func, first_cls = None, None
22
- for cell in cells:
23
- cc = getattr(cell, "cell_contents", None)
24
- if inspect.ismethod(cc):
25
- return cc
26
- if first_func is None and inspect.isfunction(cc):
27
- first_func = cc
28
- elif first_cls is None and inspect.isclass(cc):
29
- first_cls = cc
30
- return first_func or first_cls or fn
31
- except Exception:
32
- return fn
33
-
34
-
35
- def _owner_func(obj):
36
- """If obj is a bound method, return the unbound function on its owner class."""
37
- try:
38
- if inspect.ismethod(obj) and getattr(obj, "__self__", None) is not None:
39
- owner = obj.__self__.__class__
40
- name = getattr(obj, "__name__", None)
41
- if name and hasattr(owner, name):
42
- cand = getattr(owner, name)
43
- if inspect.isfunction(cand):
44
- return cand
45
- except Exception:
46
- pass
47
- return None
48
-
49
-
50
- def _find_attribute_on_target(target: Any, attr_name: str) -> Any:
51
- """Look for metadata on object, its underlying function, or owner class method."""
52
- val = getattr(target, attr_name, None)
53
- if val is not None:
54
- return val
55
- base_func = getattr(target, "__func__", None)
56
- if base_func:
57
- val = getattr(base_func, attr_name, None)
58
- if val is not None:
59
- return val
60
- of = _owner_func(target)
61
- if of:
62
- val = getattr(of, attr_name, None)
63
- if val is not None:
64
- return val
65
- return None
66
-
67
-
68
- def _has_flag(obj, flag_name: str) -> bool:
69
- return bool(_find_attribute_on_target(obj, flag_name))
70
-
71
-
72
- def _get_meta(obj, meta_name: str) -> Any:
73
- return _find_attribute_on_target(obj, meta_name)
74
-
75
-
76
- def _on_missing_meta(target):
77
- """Normalize @on_missing metadata."""
78
- meta = _get_meta(target, ON_MISSING_META)
79
- if not meta:
80
- return None
81
- return (meta.get("selector"), int(meta.get("priority", 0)))
82
-
83
-
84
- def _conditional_active(target, *, profiles: List[str]) -> bool:
85
- """Check if target is active given profiles/env/predicate."""
86
- meta = _get_meta(target, CONDITIONAL_META)
87
- if not meta:
88
- return True
89
-
90
- profs = tuple(meta.get("profiles", ()))
91
- req_env = tuple(meta.get("require_env", ()))
92
- pred = meta.get("predicate")
93
-
94
- if profs and (not profiles or not any(p in profs for p in profiles)):
95
- return False
96
- if req_env and not all(os.getenv(k) not in (None, "") for k in req_env):
97
- return False
98
- if callable(pred):
99
- try:
100
- if not bool(pred()):
101
- return False
102
- except Exception:
103
- return False
104
- return True
105
-
106
-
107
- # ------------------- public API -------------------
108
-
109
- def apply_policy(container, *, profiles: Optional[List[str]] = None) -> None:
110
- """Run all policy stages on the given container."""
111
- profiles = list(profiles or [])
112
-
113
- _filter_inactive_factory_candidates(container, profiles=profiles)
114
- _collapse_identical_keys_preferring_primary(container)
115
- _create_active_component_base_aliases(container, profiles=profiles)
116
- apply_defaults(container)
117
-
118
-
119
- def apply_defaults(container) -> None:
120
- """Bind defaults declared with @on_missing if no binding exists for selector."""
121
- defaults: dict[Any, list[tuple[int, Any]]] = {}
122
-
123
- # class components
124
- for prov_key, meta in list(container._providers.items()): # type: ignore
125
- if not isinstance(prov_key, type):
126
- continue
127
- target = _target_from_provider(meta.get("factory"))
128
- om = _on_missing_meta(target)
129
- if om:
130
- selector, prio = om
131
- defaults.setdefault(selector, []).append((prio, prov_key))
132
-
133
- # factory provides
134
- for prov_key, meta in list(container._providers.items()): # type: ignore
135
- prov = meta.get("factory")
136
- base = getattr(prov, "_pico_alias_for", None)
137
- if base is None:
138
- continue
139
- target = _target_from_provider(prov)
140
- om = _on_missing_meta(target)
141
- if om:
142
- _sel, prio = om
143
- defaults.setdefault(base, []).append((prio, prov_key))
144
-
145
- # bind highest priority candidate
146
- for base, cands in defaults.items():
147
- if container.has(base):
148
- continue
149
- cands.sort(key=lambda t: t[0], reverse=True)
150
- chosen_key = cands[0][1]
151
-
152
- def _delegate(_k=chosen_key):
153
- def _f():
154
- return container.get(_k)
155
- return _f
156
-
157
- container.bind(base, _delegate(), lazy=True)
158
-
159
-
160
- # ------------------- stages -------------------
161
-
162
- def _filter_inactive_factory_candidates(container, *, profiles: List[str]) -> None:
163
- """Remove factories inactive under profiles/env/predicate."""
164
- to_delete = []
165
- for prov_key, meta in list(container._providers.items()): # type: ignore
166
- prov = meta.get("factory")
167
- base = getattr(prov, "_pico_alias_for", None)
168
- if base is None:
169
- continue
170
- target = _target_from_provider(prov)
171
- if not _conditional_active(target, profiles=profiles):
172
- to_delete.append(prov_key)
173
- for k in to_delete:
174
- container._providers.pop(k, None) # type: ignore
175
-
176
-
177
- def _collapse_identical_keys_preferring_primary(container) -> None:
178
- """For multiple factory candidates of same base, keep one (prefer @primary)."""
179
- groups: dict[Any, list[tuple[Any, dict]]] = defaultdict(list)
180
- for k, m in list(container._providers.items()): # type: ignore
181
- prov = m.get("factory")
182
- base = getattr(prov, "_pico_alias_for", None)
183
- if base is not None:
184
- groups[base].append((k, m))
185
-
186
- for base, entries in groups.items():
187
- if not entries:
188
- continue
189
- if len(entries) == 1:
190
- keep, _ = entries[0]
191
- if (not container.has(base)) or (base != keep):
192
- factory = create_alias_provider(container, keep)
193
- container.bind(base, factory, lazy=True)
194
- continue
195
-
196
- prims = [(kk, mm) for (kk, mm) in entries if _has_flag(_target_from_provider(mm["factory"]), PRIMARY_FLAG)]
197
- if prims:
198
- keep, _ = prims[0]
199
- if (not container.has(base)) or (base != keep):
200
- factory = create_alias_provider(container, keep)
201
- container.bind(base, factory, lazy=True)
202
- for kk, _mm in entries:
203
- if kk != keep and kk != base:
204
- container._providers.pop(kk, None) # type: ignore
205
-
206
-
207
- def _create_active_component_base_aliases(container, *, profiles: List[str]) -> None:
208
- """For active class components, create base->impl aliases (prefer @primary)."""
209
- impls: List[Tuple[type, dict]] = []
210
- for key, meta in list(container._providers.items()): # type: ignore
211
- if not isinstance(key, type):
212
- continue
213
- tgt = _target_from_provider(meta.get("factory"))
214
- if _conditional_active(tgt, profiles=profiles):
215
- impls.append((key, meta))
216
-
217
- base_to_impls: Dict[Any, List[Tuple[Any, dict]]] = defaultdict(list)
218
- for impl_key, impl_meta in impls:
219
- for base in getattr(impl_key, "__mro__", ())[1:]:
220
- if base is object:
221
- break
222
- base_to_impls[base].append((impl_key, impl_meta))
223
-
224
- for base, impl_list in base_to_impls.items():
225
- if container.has(base) or not impl_list:
226
- continue
227
-
228
- regular, fallbacks = [], []
229
- for ik, im in impl_list:
230
- tgt = _target_from_provider(im["factory"])
231
- (fallbacks if _on_missing_meta(tgt) else regular).append((ik, im))
232
-
233
- def pick(cands: List[Tuple[Any, dict]]) -> Optional[Any]:
234
- if not cands:
235
- return None
236
- prims = [(ik, im) for ik, im in cands if _has_flag(_target_from_provider(im["factory"]), PRIMARY_FLAG)]
237
- return prims[0][0] if prims else cands[0][0]
238
-
239
- chosen = pick(regular) or pick(fallbacks)
240
- if not chosen:
241
- continue
242
-
243
- factory = create_alias_provider(container, chosen)
244
- container.bind(base, factory, lazy=True)
245
-
pico_ioc/proxy.py DELETED
@@ -1,115 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- from functools import lru_cache
5
- from typing import Any, Callable, Sequence
6
-
7
- from .interceptors import MethodCtx, MethodInterceptor, dispatch_method
8
-
9
- class ComponentProxy:
10
- def __init__(self, object_creator: Callable[[], Any]):
11
- object.__setattr__(self, "_object_creator", object_creator)
12
- object.__setattr__(self, "__real_object", None)
13
-
14
- def _get_real_object(self) -> Any:
15
- real = object.__getattribute__(self, "__real_object")
16
- if real is None:
17
- real = object.__getattribute__(self, "_object_creator")()
18
- object.__setattr__(self, "__real_object", real)
19
- return real
20
-
21
- @property
22
- def __class__(self):
23
- return self._get_real_object().__class__
24
-
25
- def __getattr__(self, name): return getattr(self._get_real_object(), name)
26
- def __setattr__(self, name, value): setattr(self._get_real_object(), name, value)
27
- def __delattr__(self, name): delattr(self._get_real_object(), name)
28
- def __str__(self): return str(self._get_real_object())
29
- def __repr__(self): return repr(self._get_real_object())
30
- def __dir__(self): return dir(self._get_real_object())
31
- def __len__(self): return len(self._get_real_object())
32
- def __getitem__(self, key): return self._get_real_object()[key]
33
- def __setitem__(self, key, value): self._get_real_object()[key] = value
34
- def __delitem__(self, key): del self._get_real_object()[key]
35
- def __iter__(self): return iter(self._get_real_object())
36
- def __reversed__(self): return reversed(self._get_real_object())
37
- def __contains__(self, item): return item in self._get_real_object()
38
- def __add__(self, other): return self._get_real_object() + other
39
- def __sub__(self, other): return self._get_real_object() - other
40
- def __mul__(self, other): return self._get_real_object() * other
41
- def __matmul__(self, other): return self._get_real_object() @ other
42
- def __truediv__(self, other): return self._get_real_object() / other
43
- def __floordiv__(self, other): return self._get_real_object() // other
44
- def __mod__(self, other): return self._get_real_object() % other
45
- def __divmod__(self, other): return divmod(self._get_real_object(), other)
46
- def __pow__(self, other, modulo=None): return pow(self._get_real_object(), other, modulo)
47
- def __lshift__(self, other): return self._get_real_object() << other
48
- def __rshift__(self, other): return self._get_real_object() >> other
49
- def __and__(self, other): return self._get_real_object() & other
50
- def __xor__(self, other): return self._get_real_object() ^ other
51
- def __or__(self, other): return self._get_real_object() | other
52
- def __radd__(self, other): return other + self._get_real_object()
53
- def __rsub__(self, other): return other - self._get_real_object()
54
- def __rmul__(self, other): return other * self._get_real_object()
55
- def __rmatmul__(self, other): return other @ self._get_real_object()
56
- def __rtruediv__(self, other): return other / self._get_real_object()
57
- def __rfloordiv__(self, other): return other // self._get_real_object()
58
- def __rmod__(self, other): return other % self._get_real_object()
59
- def __rdivmod__(self, other): return divmod(other, self._get_real_object())
60
- def __rpow__(self, other): return pow(other, self._get_real_object())
61
- def __rlshift__(self, other): return other << self._get_real_object()
62
- def __rrshift__(self, other): return other >> self._get_real_object()
63
- def __rand__(self, other): return other & self._get_real_object()
64
- def __rxor__(self, other): return other ^ self._get_real_object()
65
- def __ror__(self, other): return other | self._get_real_object()
66
- def __neg__(self): return -self._get_real_object()
67
- def __pos__(self): return +self._get_real_object()
68
- def __abs__(self): return abs(self._get_real_object())
69
- def __invert__(self): return ~self._get_real_object()
70
- def __eq__(self, other): return self._get_real_object() == other
71
- def __ne__(self, other): return self._get_real_object() != other
72
- def __lt__(self, other): return self._get_real_object() < other
73
- def __le__(self, other): return self._get_real_object() <= other
74
- def __gt__(self, other): return self._get_real_object() > other
75
- def __ge__(self, other): return self._get_real_object() >= other
76
- def __hash__(self): return hash(self._get_real_object())
77
- def __bool__(self): return bool(self._get_real_object())
78
- def __call__(self, *args, **kwargs): return self._get_real_object()(*args, **kwargs)
79
- def __enter__(self): return self._get_real_object().__enter__()
80
- def __exit__(self, exc_type, exc_val, exc_tb): return self._get_real_object().__exit__(exc_type, exc_val, exc_tb)
81
-
82
- class IoCProxy:
83
- __slots__ = ("_target", "_interceptors", "_container", "_request_key")
84
-
85
- def __init__(self, target: object, interceptors: Sequence[MethodInterceptor], container: Any = None, request_key: Any = None):
86
- self._target = target
87
- self._interceptors = tuple(interceptors)
88
- self._container = container
89
- self._request_key = request_key
90
-
91
- def __getattr__(self, name: str) -> Any:
92
- attr = getattr(self._target, name)
93
- if not callable(attr):
94
- return attr
95
- if hasattr(attr, "__get__"):
96
- bound_fn = attr.__get__(self._target, type(self._target))
97
- else:
98
- bound_fn = attr
99
- @lru_cache(maxsize=None)
100
- def _wrap(fn: Callable[..., Any]):
101
- if inspect.iscoroutinefunction(fn):
102
- async def aw(*args, **kwargs):
103
- ctx = MethodCtx(instance=self._target, cls=type(self._target), method=fn, name=name, args=args, kwargs=kwargs, container=self._container, request_key=self._request_key)
104
- return await dispatch_method(self._interceptors, ctx)
105
- return aw
106
- else:
107
- def sw(*args, **kwargs):
108
- ctx = MethodCtx(instance=self._target, cls=type(self._target), method=fn, name=name, args=args, kwargs=kwargs, container=self._container, request_key=self._request_key)
109
- res = dispatch_method(self._interceptors, ctx)
110
- if inspect.isawaitable(res):
111
- raise RuntimeError(f"Async interceptor on sync method: {name}")
112
- return res
113
- return sw
114
- return _wrap(bound_fn)
115
-
pico_ioc/public_api.py DELETED
@@ -1,76 +0,0 @@
1
- # pico_ioc/public_api.py
2
-
3
- from __future__ import annotations
4
- import importlib
5
- import inspect
6
- import pkgutil
7
- import sys
8
- from types import ModuleType
9
- from typing import Dict, Iterable, Optional, Tuple
10
-
11
- from .decorators import COMPONENT_FLAG, FACTORY_FLAG, PLUGIN_FLAG
12
-
13
-
14
- def export_public_symbols_decorated(
15
- *packages: str,
16
- include_also: Optional[Iterable[str]] = None,
17
- include_plugins: bool = True,
18
- ):
19
- index: Dict[str, Tuple[str, str]] = {}
20
-
21
- def _collect(m: ModuleType):
22
- names = getattr(m, "__all__", None)
23
- if isinstance(names, (list, tuple, set)):
24
- for n in names:
25
- if hasattr(m, n):
26
- index.setdefault(n, (m.__name__, n))
27
- return
28
-
29
- for n, obj in m.__dict__.items():
30
- if not inspect.isclass(obj):
31
- continue
32
- is_component = getattr(obj, COMPONENT_FLAG, False)
33
- is_factory = getattr(obj, FACTORY_FLAG, False)
34
- is_plugin = include_plugins and getattr(obj, PLUGIN_FLAG, False)
35
- if is_component or is_factory or is_plugin:
36
- index.setdefault(n, (m.__name__, n))
37
-
38
- for pkg_name in packages:
39
- try:
40
- base = importlib.import_module(pkg_name)
41
- except Exception:
42
- continue
43
- if hasattr(base, "__path__"):
44
- prefix = base.__name__ + "."
45
- for _, modname, _ in pkgutil.walk_packages(base.__path__, prefix):
46
- try:
47
- m = importlib.import_module(modname)
48
- except Exception:
49
- continue
50
- _collect(m)
51
- else:
52
- _collect(base)
53
-
54
- for qual in tuple(include_also or ()):
55
- modname, _, attr = qual.partition(":")
56
- if modname and attr:
57
- try:
58
- m = importlib.import_module(modname)
59
- if hasattr(m, attr):
60
- index.setdefault(attr, (m.__name__, attr))
61
- except Exception:
62
- pass
63
-
64
- def __getattr__(name: str):
65
- try:
66
- modname, attr = index[name]
67
- except KeyError as e:
68
- raise AttributeError(f"module has no attribute {name!r}") from e
69
- mod = sys.modules.get(modname) or importlib.import_module(modname)
70
- return getattr(mod, attr)
71
-
72
- def __dir__():
73
- return sorted(index.keys())
74
-
75
- return __getattr__, __dir__
76
-
pico_ioc/resolver.py DELETED
@@ -1,101 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- from typing import Any, Annotated, Callable, get_args, get_origin, get_type_hints
5
- from contextvars import ContextVar
6
-
7
- _path: ContextVar[list[tuple[str, str]]] = ContextVar("pico_resolve_path", default=[])
8
-
9
- def _get_hints(obj, owner_cls=None) -> dict:
10
- mod = inspect.getmodule(obj)
11
- g = getattr(mod, "__dict__", {})
12
- l = vars(owner_cls) if owner_cls is not None else None
13
- return get_type_hints(obj, globalns=g, localns=l, include_extras=True)
14
-
15
- def _is_collection_hint(tp) -> bool:
16
- origin = get_origin(tp) or tp
17
- return origin in (list, tuple)
18
-
19
- def _base_and_qualifiers_from_hint(tp):
20
- origin = get_origin(tp) or tp
21
- args = get_args(tp) or ()
22
- container_kind = list if origin is list else tuple
23
- if not args:
24
- return (object, (), container_kind)
25
- inner = args[0]
26
- if get_origin(inner) is Annotated:
27
- base, *extras = get_args(inner)
28
- quals = tuple(a for a in extras if isinstance(a, str))
29
- return (base, quals, container_kind)
30
- return (inner, (), container_kind)
31
-
32
- class Resolver:
33
- def __init__(self, container, *, prefer_name_first: bool = True):
34
- self.c = container
35
- self._prefer_name_first = bool(prefer_name_first)
36
-
37
- def _resolve_dependencies_for_callable(self, fn: Callable, owner_cls: Any = None) -> dict:
38
- sig = inspect.signature(fn)
39
- hints = _get_hints(fn, owner_cls=owner_cls)
40
- kwargs = {}
41
- path_owner = getattr(owner_cls, "__name__", getattr(fn, "__qualname__", "callable"))
42
- if fn.__name__ == "__init__" and owner_cls:
43
- path_owner = f"{path_owner}.__init__"
44
- for name, param in sig.parameters.items():
45
- if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD) or name == "self":
46
- continue
47
- ann = hints.get(name, param.annotation)
48
- st = _path.get()
49
- _path.set(st + [(path_owner, name)])
50
- try:
51
- kwargs[name] = self._resolve_param(name, ann)
52
- except NameError as e:
53
- if param.default is not inspect.Parameter.empty:
54
- _path.set(st)
55
- continue
56
- if "(required by" in str(e):
57
- raise
58
- chain = " -> ".join(f"{owner}.{param}" for owner, param in _path.get())
59
- raise NameError(f"{e} (required by {chain})") from e
60
- finally:
61
- cur = _path.get()
62
- if cur:
63
- _path.set(cur[:-1])
64
- return kwargs
65
-
66
- def create_instance(self, cls: type) -> Any:
67
- ctor_kwargs = self._resolve_dependencies_for_callable(cls.__init__, owner_cls=cls)
68
- return cls(**ctor_kwargs)
69
-
70
- def kwargs_for_callable(self, fn: Callable, *, owner_cls: Any = None) -> dict:
71
- return self._resolve_dependencies_for_callable(fn, owner_cls=owner_cls)
72
-
73
- def _notify_resolve(self, key, ann, quals=()):
74
- try:
75
- self.c._notify_resolve(key, ann, quals)
76
- except Exception:
77
- pass
78
-
79
- def _resolve_param(self, name: str, ann: Any):
80
- if _is_collection_hint(ann):
81
- base, quals, kind = _base_and_qualifiers_from_hint(ann)
82
- self._notify_resolve(base, ann, quals)
83
- items = self.c._resolve_all_for_base(base, qualifiers=quals)
84
- return list(items) if kind is list else tuple(items)
85
- if self._prefer_name_first and self.c.has(name):
86
- self._notify_resolve(name, ann, ())
87
- return self.c.get(name)
88
- if ann is not inspect._empty and self.c.has(ann):
89
- self._notify_resolve(ann, ann, ())
90
- return self.c.get(ann)
91
- if ann is not inspect._empty and isinstance(ann, type):
92
- for base in ann.__mro__[1:]:
93
- if self.c.has(base):
94
- self._notify_resolve(base, ann, ())
95
- return self.c.get(base)
96
- if self.c.has(name):
97
- self._notify_resolve(name, ann, ())
98
- return self.c.get(name)
99
- missing = ann if ann is not inspect._empty else name
100
- raise NameError(f"No provider found for key {missing!r}")
101
-