pico-ioc 1.4.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 CHANGED
@@ -1,58 +1,104 @@
1
- # pico_ioc/__init__.py
2
- try:
3
- from ._version import __version__
4
- except Exception:
5
- __version__ = "0.0.0"
6
-
7
- from .container import PicoContainer, Binder
8
- from .scope import ScopedContainer
9
- from .decorators import (
10
- component, factory_component, provides, plugin,
11
- Qualifier, qualifier,
12
- on_missing, primary, conditional, interceptor,
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,
13
14
  )
14
- from .plugins import PicoPlugin
15
- from .resolver import Resolver
16
- from .api import init, reset, scope, container_fingerprint
17
- from .proxy import ComponentProxy, IoCProxy
18
- from .interceptors import Invocation, MethodInterceptor, ContainerInterceptor
19
- from .config import (
20
- config_component, EnvSource, FileSource,
21
- Env, File, Path, Value,
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,
22
34
  )
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
23
42
 
24
43
  __all__ = [
25
- "__version__",
26
- "PicoContainer",
27
- "Binder",
28
- "PicoPlugin",
29
- "ComponentProxy",
30
- "IoCProxy",
31
- "Invocation",
32
- "MethodInterceptor",
33
- "ContainerInterceptor",
34
- "init",
35
- "scope",
36
- "reset",
37
- "container_fingerprint",
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",
38
60
  "component",
39
- "factory_component",
61
+ "factory",
40
62
  "provides",
41
- "plugin",
42
63
  "Qualifier",
43
64
  "qualifier",
44
65
  "on_missing",
45
66
  "primary",
46
67
  "conditional",
47
- "interceptor",
48
- "Resolver",
49
- "ScopedContainer",
50
- "config_component",
51
- "EnvSource",
52
- "FileSource",
53
- "Env",
54
- "File",
55
- "Path",
56
- "Value",
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",
88
+ "EnvSource",
89
+ "FileSource",
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",
57
103
  ]
58
104
 
pico_ioc/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.4.0'
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
+