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/infra.py
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import re
|
|
3
|
-
from typing import Any, Callable, Iterable, Optional, Sequence, Tuple
|
|
4
|
-
|
|
5
|
-
class Select:
|
|
6
|
-
def __init__(self):
|
|
7
|
-
self._tags: set[str] = set()
|
|
8
|
-
self._profiles: set[str] = set()
|
|
9
|
-
self._class_name_regex: Optional[re.Pattern[str]] = None
|
|
10
|
-
self._method_name_regex: Optional[re.Pattern[str]] = None
|
|
11
|
-
|
|
12
|
-
def has_tag(self, *tags: str) -> "Select":
|
|
13
|
-
self._tags.update(t for t in tags if t)
|
|
14
|
-
return self
|
|
15
|
-
|
|
16
|
-
def profile_in(self, *profiles: str) -> "Select":
|
|
17
|
-
self._profiles.update(p for p in profiles if p)
|
|
18
|
-
return self
|
|
19
|
-
|
|
20
|
-
def class_name(self, regex: str) -> "Select":
|
|
21
|
-
self._class_name_regex = re.compile(regex)
|
|
22
|
-
return self
|
|
23
|
-
|
|
24
|
-
def method_name(self, regex: str) -> "Select":
|
|
25
|
-
self._method_name_regex = re.compile(regex)
|
|
26
|
-
return self
|
|
27
|
-
|
|
28
|
-
def is_effectively_empty(self) -> bool:
|
|
29
|
-
return not (self._tags or self._profiles or self._class_name_regex or self._method_name_regex)
|
|
30
|
-
|
|
31
|
-
def match_provider(self, key: Any, meta: dict, *, active_profiles: Sequence[str]) -> bool:
|
|
32
|
-
if self.is_effectively_empty():
|
|
33
|
-
return False
|
|
34
|
-
if self._tags:
|
|
35
|
-
tags = set(meta.get("tags", ()))
|
|
36
|
-
if not tags.intersection(self._tags):
|
|
37
|
-
return False
|
|
38
|
-
if self._profiles:
|
|
39
|
-
if not set(active_profiles).intersection(self._profiles):
|
|
40
|
-
return False
|
|
41
|
-
if self._class_name_regex and isinstance(key, type):
|
|
42
|
-
if not self._class_name_regex.search(getattr(key, "__name__", "")):
|
|
43
|
-
return False
|
|
44
|
-
return True
|
|
45
|
-
|
|
46
|
-
def match_method_name(self, method: str) -> bool:
|
|
47
|
-
if self._method_name_regex is None:
|
|
48
|
-
return True
|
|
49
|
-
return bool(self._method_name_regex.search(method))
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class InfraQuery:
|
|
53
|
-
def __init__(self, container, profiles: Tuple[str, ...]):
|
|
54
|
-
self.c = container
|
|
55
|
-
self.profiles = profiles
|
|
56
|
-
|
|
57
|
-
def providers(self, where: Optional[Select] = None, *, limit: Optional[int] = None) -> list[tuple[Any, dict]]:
|
|
58
|
-
sel = where or Select()
|
|
59
|
-
items: list[tuple[Any, dict]] = []
|
|
60
|
-
for k, m in self.c._providers.items():
|
|
61
|
-
if sel.match_provider(k, m, active_profiles=self.profiles):
|
|
62
|
-
items.append((k, m))
|
|
63
|
-
if limit is not None and len(items) >= limit:
|
|
64
|
-
break
|
|
65
|
-
return items
|
|
66
|
-
|
|
67
|
-
def components(self, where: Optional[Select] = None, *, limit: Optional[int] = None) -> list[type]:
|
|
68
|
-
out: list[type] = []
|
|
69
|
-
for k, _m in self.providers(where=where, limit=limit):
|
|
70
|
-
if isinstance(k, type):
|
|
71
|
-
out.append(k)
|
|
72
|
-
return out
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class InfraIntercept:
|
|
76
|
-
def __init__(self, container, profiles: Tuple[str, ...]):
|
|
77
|
-
self.c = container
|
|
78
|
-
self.profiles = profiles
|
|
79
|
-
self._per_method_cap: Optional[int] = None
|
|
80
|
-
|
|
81
|
-
def _collect_target_classes(self, where: Select) -> tuple[set[type], set[Any]]:
|
|
82
|
-
classes: set[type] = set()
|
|
83
|
-
keys: set[Any] = set()
|
|
84
|
-
for key, meta in self.c._providers.items():
|
|
85
|
-
if where.match_provider(key, meta, active_profiles=self.profiles):
|
|
86
|
-
keys.add(key)
|
|
87
|
-
if isinstance(key, type):
|
|
88
|
-
classes.add(key)
|
|
89
|
-
return classes, keys
|
|
90
|
-
|
|
91
|
-
def _guard_method_interceptor(self, interceptor, where: Select):
|
|
92
|
-
target_classes, _keys = self._collect_target_classes(where)
|
|
93
|
-
class_names = {cls.__name__ for cls in target_classes}
|
|
94
|
-
class Guarded:
|
|
95
|
-
def invoke(self, ctx, call_next):
|
|
96
|
-
tgt_cls = type(ctx.instance)
|
|
97
|
-
ok_class = any(isinstance(ctx.instance, cls) for cls in target_classes) or (getattr(tgt_cls, "__name__", "") in class_names)
|
|
98
|
-
ok_method = where.match_method_name(ctx.name)
|
|
99
|
-
if ok_class and ok_method:
|
|
100
|
-
return interceptor.invoke(ctx, call_next) if hasattr(interceptor, "invoke") else interceptor(ctx, call_next)
|
|
101
|
-
return call_next(ctx)
|
|
102
|
-
return Guarded()
|
|
103
|
-
|
|
104
|
-
def _guard_container_interceptor(self, interceptor, where: Select):
|
|
105
|
-
target_classes, keys = self._collect_target_classes(where)
|
|
106
|
-
class_names = {cls.__name__ for cls in target_classes}
|
|
107
|
-
def _ok(key: Any) -> bool:
|
|
108
|
-
if key in keys:
|
|
109
|
-
return True
|
|
110
|
-
if isinstance(key, type) and (key in target_classes or getattr(key, "__name__", "") in class_names):
|
|
111
|
-
return True
|
|
112
|
-
return False
|
|
113
|
-
class GuardedCI:
|
|
114
|
-
def around_resolve(self, ctx, call_next):
|
|
115
|
-
if _ok(ctx.key):
|
|
116
|
-
return interceptor.around_resolve(ctx, call_next)
|
|
117
|
-
return call_next(ctx)
|
|
118
|
-
def around_create(self, ctx, call_next):
|
|
119
|
-
if _ok(ctx.key):
|
|
120
|
-
return interceptor.around_create(ctx, call_next)
|
|
121
|
-
return call_next(ctx)
|
|
122
|
-
return GuardedCI()
|
|
123
|
-
|
|
124
|
-
def add(self, *, interceptor, where: Select) -> None:
|
|
125
|
-
sel = where or Select()
|
|
126
|
-
if sel.is_effectively_empty():
|
|
127
|
-
raise ValueError("empty selector for interceptor")
|
|
128
|
-
is_container = all(hasattr(interceptor, m) for m in ("around_resolve", "around_create"))
|
|
129
|
-
if is_container:
|
|
130
|
-
guarded = self._guard_container_interceptor(interceptor, sel)
|
|
131
|
-
self.c.add_container_interceptor(guarded)
|
|
132
|
-
return
|
|
133
|
-
guarded = self._guard_method_interceptor(interceptor, sel)
|
|
134
|
-
self.c.add_method_interceptor(guarded)
|
|
135
|
-
|
|
136
|
-
def limit_per_method(self, max_n: int) -> None:
|
|
137
|
-
self._per_method_cap = int(max_n)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class InfraMutate:
|
|
141
|
-
def __init__(self, container, profiles: Tuple[str, ...]):
|
|
142
|
-
self.c = container
|
|
143
|
-
self.profiles = profiles
|
|
144
|
-
|
|
145
|
-
def add_tags(self, component_or_key: Any, tags: Iterable[str]) -> None:
|
|
146
|
-
key = component_or_key
|
|
147
|
-
if key in self.c._providers:
|
|
148
|
-
meta = self.c._providers[key]
|
|
149
|
-
cur = tuple(meta.get("tags", ()))
|
|
150
|
-
new = tuple(dict.fromkeys(list(cur) + [t for t in tags if t]))
|
|
151
|
-
meta["tags"] = new
|
|
152
|
-
self.c._providers[key] = meta
|
|
153
|
-
|
|
154
|
-
def set_qualifiers(self, provider_key: Any, qualifiers: dict[str, Any]) -> None:
|
|
155
|
-
if provider_key in self.c._providers:
|
|
156
|
-
meta = self.c._providers[provider_key]
|
|
157
|
-
meta["qualifiers"] = tuple(qualifiers or ())
|
|
158
|
-
self.c._providers[provider_key] = meta
|
|
159
|
-
|
|
160
|
-
def replace_provider(self, *, key: Any, with_factory: Callable[[], object]) -> None:
|
|
161
|
-
if key in self.c._providers:
|
|
162
|
-
lazy = bool(self.c._providers[key].get("lazy", False))
|
|
163
|
-
self.c.bind(key, with_factory, lazy=lazy, tags=self.c._providers[key].get("tags", ()))
|
|
164
|
-
|
|
165
|
-
def wrap_provider(self, *, key: Any, around: Callable[[Callable[[], object]], Callable[[], object]]) -> None:
|
|
166
|
-
if key in self.c._providers:
|
|
167
|
-
meta = self.c._providers[key]
|
|
168
|
-
base_factory = meta.get("factory")
|
|
169
|
-
if callable(base_factory):
|
|
170
|
-
wrapped = around(base_factory)
|
|
171
|
-
self.c.bind(key, wrapped, lazy=bool(meta.get("lazy", False)), tags=meta.get("tags", ()))
|
|
172
|
-
|
|
173
|
-
def rename_key(self, *, old: Any, new: Any) -> None:
|
|
174
|
-
if old in self.c._providers and new not in self.c._providers:
|
|
175
|
-
self.c._providers[new] = self.c._providers.pop(old)
|
|
176
|
-
|
|
177
|
-
class Infra:
|
|
178
|
-
def __init__(self, *, container, profiles: Tuple[str, ...]):
|
|
179
|
-
self._c = container
|
|
180
|
-
self._profiles = profiles
|
|
181
|
-
self._query = InfraQuery(container, profiles)
|
|
182
|
-
self._intercept = InfraIntercept(container, profiles)
|
|
183
|
-
self._mutate = InfraMutate(container, profiles)
|
|
184
|
-
|
|
185
|
-
@property
|
|
186
|
-
def query(self) -> InfraQuery:
|
|
187
|
-
return self._query
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def intercept(self) -> InfraIntercept:
|
|
191
|
-
return self._intercept
|
|
192
|
-
|
|
193
|
-
@property
|
|
194
|
-
def mutate(self) -> InfraMutate:
|
|
195
|
-
return self._mutate
|
|
196
|
-
|
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
|
-
|