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/__init__.py
CHANGED
|
@@ -1,68 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from .resolver import Resolver
|
|
15
|
-
from .api import init, reset, scope, container_fingerprint
|
|
16
|
-
from .proxy import ComponentProxy, IoCProxy
|
|
17
|
-
from .interceptors import (
|
|
18
|
-
MethodInterceptor,
|
|
19
|
-
ContainerInterceptor,
|
|
20
|
-
MethodCtx,
|
|
21
|
-
ResolveCtx,
|
|
22
|
-
CreateCtx,
|
|
1
|
+
# src/pico_ioc/__init__.py
|
|
2
|
+
from .constants import LOGGER_NAME, LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
PicoError,
|
|
5
|
+
ProviderNotFoundError,
|
|
6
|
+
CircularDependencyError,
|
|
7
|
+
ComponentCreationError,
|
|
8
|
+
ScopeError,
|
|
9
|
+
ConfigurationError,
|
|
10
|
+
SerializationError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
InvalidBindingError,
|
|
13
|
+
EventBusClosedError,
|
|
23
14
|
)
|
|
24
|
-
from .
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
from .api import (
|
|
16
|
+
component,
|
|
17
|
+
factory,
|
|
18
|
+
provides,
|
|
19
|
+
Qualifier,
|
|
20
|
+
qualifier,
|
|
21
|
+
on_missing,
|
|
22
|
+
primary,
|
|
23
|
+
conditional,
|
|
24
|
+
lazy,
|
|
25
|
+
configuration,
|
|
26
|
+
configure,
|
|
27
|
+
cleanup,
|
|
28
|
+
scope,
|
|
29
|
+
ConfigSource,
|
|
30
|
+
EnvSource,
|
|
31
|
+
FileSource,
|
|
32
|
+
init,
|
|
33
|
+
configured,
|
|
27
34
|
)
|
|
28
|
-
from .
|
|
35
|
+
from .scope import ScopeManager, ContextVarScope, ScopeProtocol, ScopedCaches
|
|
36
|
+
from .locator import ComponentLocator
|
|
37
|
+
from .factory import ComponentFactory, ProviderMetadata, DeferredProvider
|
|
38
|
+
from .aop import MethodCtx, MethodInterceptor, intercepted_by, UnifiedComponentProxy, health, ContainerObserver
|
|
39
|
+
from .container import PicoContainer
|
|
40
|
+
from .event_bus import EventBus, ExecPolicy, ErrorPolicy, Event, subscribe, AutoSubscriberMixin
|
|
41
|
+
from .config_runtime import JsonTreeSource, YamlTreeSource, DictSource, Discriminator
|
|
29
42
|
|
|
30
43
|
__all__ = [
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"LOGGER_NAME",
|
|
45
|
+
"LOGGER",
|
|
46
|
+
"PICO_INFRA",
|
|
47
|
+
"PICO_NAME",
|
|
48
|
+
"PICO_KEY",
|
|
49
|
+
"PICO_META",
|
|
50
|
+
"PicoError",
|
|
51
|
+
"ProviderNotFoundError",
|
|
52
|
+
"CircularDependencyError",
|
|
53
|
+
"ComponentCreationError",
|
|
54
|
+
"ScopeError",
|
|
55
|
+
"ConfigurationError",
|
|
56
|
+
"SerializationError",
|
|
57
|
+
"ValidationError",
|
|
58
|
+
"InvalidBindingError",
|
|
59
|
+
"EventBusClosedError",
|
|
46
60
|
"component",
|
|
47
|
-
"
|
|
61
|
+
"factory",
|
|
48
62
|
"provides",
|
|
49
|
-
"plugin",
|
|
50
63
|
"Qualifier",
|
|
51
64
|
"qualifier",
|
|
52
65
|
"on_missing",
|
|
53
66
|
"primary",
|
|
54
67
|
"conditional",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
68
|
+
"lazy",
|
|
69
|
+
"configuration",
|
|
70
|
+
"configure",
|
|
71
|
+
"cleanup",
|
|
72
|
+
"scope",
|
|
73
|
+
"ScopeProtocol",
|
|
74
|
+
"ContextVarScope",
|
|
75
|
+
"ScopeManager",
|
|
76
|
+
"ComponentLocator",
|
|
77
|
+
"ScopedCaches",
|
|
78
|
+
"ProviderMetadata",
|
|
79
|
+
"ComponentFactory",
|
|
80
|
+
"DeferredProvider",
|
|
81
|
+
"MethodCtx",
|
|
82
|
+
"MethodInterceptor",
|
|
83
|
+
"intercepted_by",
|
|
84
|
+
"UnifiedComponentProxy",
|
|
85
|
+
"health",
|
|
86
|
+
"ContainerObserver",
|
|
87
|
+
"PicoContainer",
|
|
59
88
|
"EnvSource",
|
|
60
89
|
"FileSource",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
90
|
+
"ConfigSource",
|
|
91
|
+
"init",
|
|
92
|
+
"configured",
|
|
93
|
+
"EventBus",
|
|
94
|
+
"ExecPolicy",
|
|
95
|
+
"ErrorPolicy",
|
|
96
|
+
"Event",
|
|
97
|
+
"subscribe",
|
|
98
|
+
"AutoSubscriberMixin",
|
|
99
|
+
"JsonTreeSource",
|
|
100
|
+
"YamlTreeSource",
|
|
101
|
+
"DictSource",
|
|
102
|
+
"Discriminator",
|
|
67
103
|
]
|
|
68
104
|
|
pico_ioc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '
|
|
1
|
+
__version__ = '2.0.0'
|
pico_ioc/aop.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# src/pico_ioc/aop.py
|
|
2
|
+
import inspect
|
|
3
|
+
import pickle
|
|
4
|
+
from typing import Any, Callable, Dict, List, Tuple, Protocol, Union
|
|
5
|
+
from .exceptions import SerializationError
|
|
6
|
+
|
|
7
|
+
KeyT = Union[str, type]
|
|
8
|
+
|
|
9
|
+
class MethodCtx:
|
|
10
|
+
__slots__ = ("instance", "cls", "method", "name", "args", "kwargs", "container", "local", "request_key")
|
|
11
|
+
def __init__(self, *, instance: object, cls: type, method: Callable[..., Any], name: str, args: tuple, kwargs: dict, container: Any, request_key: Any = None):
|
|
12
|
+
self.instance = instance
|
|
13
|
+
self.cls = cls
|
|
14
|
+
self.method = method
|
|
15
|
+
self.name = name
|
|
16
|
+
self.args = args
|
|
17
|
+
self.kwargs = kwargs
|
|
18
|
+
self.container = container
|
|
19
|
+
self.local: Dict[str, Any] = {}
|
|
20
|
+
self.request_key = request_key
|
|
21
|
+
|
|
22
|
+
class MethodInterceptor(Protocol):
|
|
23
|
+
def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any: ...
|
|
24
|
+
|
|
25
|
+
class ContainerObserver(Protocol):
|
|
26
|
+
def on_resolve(self, key: KeyT, took_ms: float): ...
|
|
27
|
+
def on_cache_hit(self, key: KeyT): ...
|
|
28
|
+
|
|
29
|
+
def dispatch_method(interceptors: List["MethodInterceptor"], ctx: MethodCtx) -> Any:
|
|
30
|
+
idx = 0
|
|
31
|
+
def call_next(next_ctx: MethodCtx) -> Any:
|
|
32
|
+
nonlocal idx
|
|
33
|
+
if idx >= len(interceptors):
|
|
34
|
+
return next_ctx.method(*next_ctx.args, **next_ctx.kwargs)
|
|
35
|
+
interceptor = interceptors[idx]
|
|
36
|
+
idx += 1
|
|
37
|
+
return interceptor.invoke(next_ctx, call_next)
|
|
38
|
+
return call_next(ctx)
|
|
39
|
+
|
|
40
|
+
def intercepted_by(*interceptor_classes: type["MethodInterceptor"]):
|
|
41
|
+
if not interceptor_classes:
|
|
42
|
+
raise TypeError("intercepted_by requires at least one interceptor class")
|
|
43
|
+
for ic in interceptor_classes:
|
|
44
|
+
if not inspect.isclass(ic):
|
|
45
|
+
raise TypeError("intercepted_by expects interceptor classes")
|
|
46
|
+
def dec(fn):
|
|
47
|
+
if not (inspect.isfunction(fn) or inspect.ismethod(fn) or inspect.iscoroutinefunction(fn)):
|
|
48
|
+
raise TypeError("intercepted_by can only decorate callables")
|
|
49
|
+
existing = list(getattr(fn, "_pico_interceptors_", []))
|
|
50
|
+
for cls in interceptor_classes:
|
|
51
|
+
if cls not in existing:
|
|
52
|
+
existing.append(cls)
|
|
53
|
+
setattr(fn, "_pico_interceptors_", tuple(existing))
|
|
54
|
+
return fn
|
|
55
|
+
return dec
|
|
56
|
+
|
|
57
|
+
def _gather_interceptors_for_method(target_cls: type, name: str) -> Tuple[type, ...]:
|
|
58
|
+
try:
|
|
59
|
+
original = getattr(target_cls, name)
|
|
60
|
+
except AttributeError:
|
|
61
|
+
return ()
|
|
62
|
+
if inspect.ismethoddescriptor(original) or inspect.isbuiltin(original):
|
|
63
|
+
return ()
|
|
64
|
+
return tuple(getattr(original, "_pico_interceptors_", ()))
|
|
65
|
+
|
|
66
|
+
def health(fn):
|
|
67
|
+
from .constants import PICO_META
|
|
68
|
+
meta = getattr(fn, PICO_META, None)
|
|
69
|
+
if meta is None:
|
|
70
|
+
meta = {}
|
|
71
|
+
setattr(fn, PICO_META, meta)
|
|
72
|
+
meta["health_check"] = True
|
|
73
|
+
return fn
|
|
74
|
+
|
|
75
|
+
class UnifiedComponentProxy:
|
|
76
|
+
__slots__ = ("_target", "_creator", "_container", "_cache")
|
|
77
|
+
def __init__(self, *, container: Any, target: Any = None, object_creator: Callable[[], Any] | None = None):
|
|
78
|
+
if container is None:
|
|
79
|
+
raise ValueError("UnifiedComponentProxy requires a non-null container")
|
|
80
|
+
if target is None and object_creator is None:
|
|
81
|
+
raise ValueError("UnifiedComponentProxy requires either a target or an object_creator")
|
|
82
|
+
object.__setattr__(self, "_container", container)
|
|
83
|
+
object.__setattr__(self, "_target", target)
|
|
84
|
+
object.__setattr__(self, "_creator", object_creator)
|
|
85
|
+
object.__setattr__(self, "_cache", {})
|
|
86
|
+
|
|
87
|
+
def _get_real_object(self) -> Any:
|
|
88
|
+
tgt = object.__getattribute__(self, "_target")
|
|
89
|
+
if tgt is not None:
|
|
90
|
+
return tgt
|
|
91
|
+
creator = object.__getattribute__(self, "_creator")
|
|
92
|
+
if not callable(creator):
|
|
93
|
+
raise TypeError("UnifiedComponentProxy object_creator must be callable")
|
|
94
|
+
tgt = creator()
|
|
95
|
+
if tgt is None:
|
|
96
|
+
raise RuntimeError("UnifiedComponentProxy object_creator returned None")
|
|
97
|
+
object.__setattr__(self, "_target", tgt)
|
|
98
|
+
return tgt
|
|
99
|
+
|
|
100
|
+
def _scope_signature(self) -> Tuple[Any, ...]:
|
|
101
|
+
container = object.__getattribute__(self, "_container")
|
|
102
|
+
target = object.__getattribute__(self, "_target")
|
|
103
|
+
loc = getattr(container, "_locator", None)
|
|
104
|
+
if not loc:
|
|
105
|
+
return ()
|
|
106
|
+
if target is not None:
|
|
107
|
+
t = type(target)
|
|
108
|
+
for k, md in loc._metadata.items():
|
|
109
|
+
typ = md.provided_type or md.concrete_class
|
|
110
|
+
if isinstance(typ, type) and t is typ:
|
|
111
|
+
sc = md.scope
|
|
112
|
+
if sc == "singleton":
|
|
113
|
+
return ()
|
|
114
|
+
return (container.scopes.get_id(sc),)
|
|
115
|
+
return ()
|
|
116
|
+
|
|
117
|
+
def _build_wrapped(self, name: str, bound: Callable[..., Any], interceptors_cls: Tuple[type, ...]):
|
|
118
|
+
container = object.__getattribute__(self, "_container")
|
|
119
|
+
interceptors = [container.get(cls) for cls in interceptors_cls]
|
|
120
|
+
sig = self._scope_signature()
|
|
121
|
+
target = self._get_real_object()
|
|
122
|
+
original_func = bound
|
|
123
|
+
if hasattr(bound, '__func__'):
|
|
124
|
+
original_func = bound.__func__
|
|
125
|
+
|
|
126
|
+
if inspect.iscoroutinefunction(original_func):
|
|
127
|
+
async def aw(*args, **kwargs):
|
|
128
|
+
ctx = MethodCtx(
|
|
129
|
+
instance=target,
|
|
130
|
+
cls=type(target),
|
|
131
|
+
method=bound,
|
|
132
|
+
name=name,
|
|
133
|
+
args=args,
|
|
134
|
+
kwargs=kwargs,
|
|
135
|
+
container=container,
|
|
136
|
+
request_key=sig[0] if sig else None
|
|
137
|
+
)
|
|
138
|
+
res = dispatch_method(interceptors, ctx)
|
|
139
|
+
if inspect.isawaitable(res):
|
|
140
|
+
return await res
|
|
141
|
+
return res
|
|
142
|
+
return sig, aw, interceptors_cls
|
|
143
|
+
else:
|
|
144
|
+
def sw(*args, **kwargs):
|
|
145
|
+
ctx = MethodCtx(
|
|
146
|
+
instance=target,
|
|
147
|
+
cls=type(target),
|
|
148
|
+
method=bound,
|
|
149
|
+
name=name,
|
|
150
|
+
args=args,
|
|
151
|
+
kwargs=kwargs,
|
|
152
|
+
container=container,
|
|
153
|
+
request_key=sig[0] if sig else None
|
|
154
|
+
)
|
|
155
|
+
res = dispatch_method(interceptors, ctx)
|
|
156
|
+
if inspect.isawaitable(res):
|
|
157
|
+
raise RuntimeError(f"Async interceptor returned awaitable on sync method: {name}")
|
|
158
|
+
return res
|
|
159
|
+
return sig, sw, interceptors_cls
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def __class__(self):
|
|
163
|
+
return self._get_real_object().__class__
|
|
164
|
+
|
|
165
|
+
def __getattr__(self, name: str) -> Any:
|
|
166
|
+
target = self._get_real_object()
|
|
167
|
+
attr = getattr(target, name)
|
|
168
|
+
if not callable(attr):
|
|
169
|
+
return attr
|
|
170
|
+
interceptors_cls = _gather_interceptors_for_method(type(target), name)
|
|
171
|
+
if not interceptors_cls:
|
|
172
|
+
return attr
|
|
173
|
+
cache: Dict[str, Tuple[Tuple[Any, ...], Callable[..., Any], Tuple[type, ...]]] = object.__getattribute__(self, "_cache")
|
|
174
|
+
cur_sig = self._scope_signature()
|
|
175
|
+
cached = cache.get(name)
|
|
176
|
+
if cached is not None:
|
|
177
|
+
sig, wrapped, cls_tuple = cached
|
|
178
|
+
if sig == cur_sig and cls_tuple == interceptors_cls:
|
|
179
|
+
return wrapped
|
|
180
|
+
sig, wrapped, cls_tuple = self._build_wrapped(name, attr, interceptors_cls)
|
|
181
|
+
cache[name] = (sig, wrapped, cls_tuple)
|
|
182
|
+
return wrapped
|
|
183
|
+
|
|
184
|
+
def __setattr__(self, name, value): setattr(self._get_real_object(), name, value)
|
|
185
|
+
def __delattr__(self, name): delattr(self._get_real_object(), name)
|
|
186
|
+
def __str__(self): return str(self._get_real_object())
|
|
187
|
+
def __repr__(self): return repr(self._get_real_object())
|
|
188
|
+
def __dir__(self): return dir(self._get_real_object())
|
|
189
|
+
def __len__(self): return len(self._get_real_object())
|
|
190
|
+
def __getitem__(self, key): return self._get_real_object()[key]
|
|
191
|
+
def __setitem__(self, key, value): self._get_real_object()[key] = value
|
|
192
|
+
def __delitem__(self, key): del self._get_real_object()[key]
|
|
193
|
+
def __iter__(self): return iter(self._get_real_object())
|
|
194
|
+
def __reversed__(self): return reversed(self._get_real_object())
|
|
195
|
+
def __contains__(self, item): return item in self._get_real_object()
|
|
196
|
+
def __add__(self, other): return self._get_real_object() + other
|
|
197
|
+
def __sub__(self, other): return self._get_real_object() - other
|
|
198
|
+
def __mul__(self, other): return self._get_real_object() * other
|
|
199
|
+
def __matmul__(self, other): return self._get_real_object() @ other
|
|
200
|
+
def __truediv__(self, other): return self._get_real_object() / other
|
|
201
|
+
def __floordiv__(self, other): return self._get_real_object() // other
|
|
202
|
+
def __mod__(self, other): return self._get_real_object() % other
|
|
203
|
+
def __divmod__(self, other): return divmod(self._get_real_object(), other)
|
|
204
|
+
def __pow__(self, other, modulo=None): return pow(self._get_real_object(), other, modulo)
|
|
205
|
+
def __lshift__(self, other): return self._get_real_object() << other
|
|
206
|
+
def __rshift__(self, other): return self._get_real_object() >> other
|
|
207
|
+
def __and__(self, other): return self._get_real_object() & other
|
|
208
|
+
def __xor__(self, other): return self._get_real_object() ^ other
|
|
209
|
+
def __or__(self, other): return self._get_real_object() | other
|
|
210
|
+
def __radd__(self, other): return other + self._get_real_object()
|
|
211
|
+
def __rsub__(self, other): return other - self._get_real_object()
|
|
212
|
+
def __rmul__(self, other): return other * self._get_real_object()
|
|
213
|
+
def __rmatmul__(self, other): return other @ self._get_real_object()
|
|
214
|
+
def __rtruediv__(self, other): return other / self._get_real_object()
|
|
215
|
+
def __rfloordiv__(self, other): return other // self._get_real_object()
|
|
216
|
+
def __rmod__(self, other): return other % self._get_real_object()
|
|
217
|
+
def __rdivmod__(self, other): return divmod(other, self._get_real_object())
|
|
218
|
+
def __rpow__(self, other): return pow(other, self._get_real_object())
|
|
219
|
+
def __rlshift__(self, other): return other << self._get_real_object()
|
|
220
|
+
def __rrshift__(self, other): return other >> self._get_real_object()
|
|
221
|
+
def __rand__(self, other): return other & self._get_real_object()
|
|
222
|
+
def __rxor__(self, other): return other ^ self._get_real_object()
|
|
223
|
+
def __ror__(self, other): return other | self._get_real_object()
|
|
224
|
+
def __neg__(self): return -self._get_real_object()
|
|
225
|
+
def __pos__(self): return +self._get_real_object()
|
|
226
|
+
def __abs__(self): return abs(self._get_real_object())
|
|
227
|
+
def __invert__(self): return ~self._get_real_object()
|
|
228
|
+
def __eq__(self, other): return self._get_real_object() == other
|
|
229
|
+
def __ne__(self, other): return self._get_real_object() != other
|
|
230
|
+
def __lt__(self, other): return self._get_real_object() < other
|
|
231
|
+
def __le__(self, other): return self._get_real_object() <= other
|
|
232
|
+
def __gt__(self, other): return self._get_real_object() > other
|
|
233
|
+
def __ge__(self, other): return self._get_real_object() >= other
|
|
234
|
+
def __hash__(self): return hash(self._get_real_object())
|
|
235
|
+
def __bool__(self): return bool(self._get_real_object())
|
|
236
|
+
def __call__(self, *args, **kwargs): return self._get_real_object()(*args, **kwargs)
|
|
237
|
+
def __enter__(self): return self._get_real_object().__enter__()
|
|
238
|
+
def __exit__(self, exc_type, exc_val, exc_tb): return self._get_real_object().__exit__(exc_type, exc_val, exc_tb)
|
|
239
|
+
|
|
240
|
+
def __reduce_ex__(self, protocol):
|
|
241
|
+
o = self._get_real_object()
|
|
242
|
+
try:
|
|
243
|
+
data = pickle.dumps(o, protocol=protocol)
|
|
244
|
+
return (pickle.loads, (data,))
|
|
245
|
+
except Exception as e:
|
|
246
|
+
raise SerializationError(f"Proxy target is not serializable: {e}")
|
|
247
|
+
|