pico-ioc 2.0.4__py3-none-any.whl → 2.1.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 +7 -11
- pico_ioc/_version.py +1 -1
- pico_ioc/analysis.py +92 -0
- pico_ioc/aop.py +3 -6
- pico_ioc/api.py +27 -1084
- pico_ioc/component_scanner.py +166 -0
- pico_ioc/config_builder.py +91 -0
- pico_ioc/config_registrar.py +219 -0
- pico_ioc/config_runtime.py +17 -2
- pico_ioc/container.py +197 -156
- pico_ioc/decorators.py +192 -0
- pico_ioc/dependency_validator.py +103 -0
- pico_ioc/exceptions.py +0 -16
- pico_ioc/factory.py +2 -1
- pico_ioc/locator.py +80 -2
- pico_ioc/provider_selector.py +35 -0
- pico_ioc/registrar.py +169 -0
- {pico_ioc-2.0.4.dist-info → pico_ioc-2.1.0.dist-info}/METADATA +93 -44
- pico_ioc-2.1.0.dist-info/RECORD +25 -0
- pico_ioc-2.0.4.dist-info/RECORD +0 -17
- {pico_ioc-2.0.4.dist-info → pico_ioc-2.1.0.dist-info}/WHEEL +0 -0
- {pico_ioc-2.0.4.dist-info → pico_ioc-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.0.4.dist-info → pico_ioc-2.1.0.dist-info}/top_level.txt +0 -0
pico_ioc/__init__.py
CHANGED
|
@@ -3,7 +3,6 @@ from .constants import LOGGER_NAME, LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PIC
|
|
|
3
3
|
from .exceptions import (
|
|
4
4
|
PicoError,
|
|
5
5
|
ProviderNotFoundError,
|
|
6
|
-
CircularDependencyError,
|
|
7
6
|
ComponentCreationError,
|
|
8
7
|
ScopeError,
|
|
9
8
|
ConfigurationError,
|
|
@@ -18,16 +17,12 @@ from .api import (
|
|
|
18
17
|
factory,
|
|
19
18
|
provides,
|
|
20
19
|
Qualifier,
|
|
21
|
-
configuration,
|
|
22
20
|
configure,
|
|
23
21
|
cleanup,
|
|
24
|
-
ConfigSource,
|
|
25
|
-
EnvSource,
|
|
26
|
-
FileSource,
|
|
27
|
-
FlatDictSource,
|
|
28
22
|
init,
|
|
29
23
|
configured,
|
|
30
24
|
)
|
|
25
|
+
from .config_builder import configuration, ContextConfig, EnvSource, FileSource, FlatDictSource, Value
|
|
31
26
|
from .scope import ScopeManager, ContextVarScope, ScopeProtocol, ScopedCaches
|
|
32
27
|
from .locator import ComponentLocator
|
|
33
28
|
from .factory import ComponentFactory, ProviderMetadata, DeferredProvider
|
|
@@ -35,6 +30,7 @@ from .aop import MethodCtx, MethodInterceptor, intercepted_by, UnifiedComponentP
|
|
|
35
30
|
from .container import PicoContainer
|
|
36
31
|
from .event_bus import EventBus, ExecPolicy, ErrorPolicy, Event, subscribe, AutoSubscriberMixin
|
|
37
32
|
from .config_runtime import JsonTreeSource, YamlTreeSource, DictSource, Discriminator
|
|
33
|
+
from .analysis import DependencyRequest, analyze_callable_dependencies
|
|
38
34
|
|
|
39
35
|
__all__ = [
|
|
40
36
|
"LOGGER_NAME",
|
|
@@ -45,7 +41,6 @@ __all__ = [
|
|
|
45
41
|
"PICO_META",
|
|
46
42
|
"PicoError",
|
|
47
43
|
"ProviderNotFoundError",
|
|
48
|
-
"CircularDependencyError",
|
|
49
44
|
"ComponentCreationError",
|
|
50
45
|
"ScopeError",
|
|
51
46
|
"ConfigurationError",
|
|
@@ -58,7 +53,6 @@ __all__ = [
|
|
|
58
53
|
"factory",
|
|
59
54
|
"provides",
|
|
60
55
|
"Qualifier",
|
|
61
|
-
"configuration",
|
|
62
56
|
"configure",
|
|
63
57
|
"cleanup",
|
|
64
58
|
"ScopeProtocol",
|
|
@@ -78,10 +72,12 @@ __all__ = [
|
|
|
78
72
|
"PicoContainer",
|
|
79
73
|
"EnvSource",
|
|
80
74
|
"FileSource",
|
|
81
|
-
"ConfigSource",
|
|
82
75
|
"FlatDictSource",
|
|
83
76
|
"init",
|
|
84
77
|
"configured",
|
|
78
|
+
"configuration",
|
|
79
|
+
"ContextConfig",
|
|
80
|
+
"Value",
|
|
85
81
|
"EventBus",
|
|
86
82
|
"ExecPolicy",
|
|
87
83
|
"ErrorPolicy",
|
|
@@ -92,6 +88,6 @@ __all__ = [
|
|
|
92
88
|
"YamlTreeSource",
|
|
93
89
|
"DictSource",
|
|
94
90
|
"Discriminator",
|
|
91
|
+
"DependencyRequest",
|
|
92
|
+
"analyze_callable_dependencies",
|
|
95
93
|
]
|
|
96
|
-
|
|
97
|
-
|
pico_ioc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.0
|
|
1
|
+
__version__ = '2.1.0'
|
pico_ioc/analysis.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Callable, List, Optional, Tuple, Union, get_args, get_origin, Annotated
|
|
4
|
+
from .decorators import Qualifier
|
|
5
|
+
|
|
6
|
+
KeyT = Union[str, type]
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class DependencyRequest:
|
|
10
|
+
parameter_name: str
|
|
11
|
+
key: KeyT
|
|
12
|
+
is_list: bool = False
|
|
13
|
+
qualifier: Optional[str] = None
|
|
14
|
+
is_optional: bool = False
|
|
15
|
+
|
|
16
|
+
def _extract_annotated(ann: Any) -> Tuple[Any, Optional[str]]:
|
|
17
|
+
qualifier = None
|
|
18
|
+
base = ann
|
|
19
|
+
origin = get_origin(ann)
|
|
20
|
+
|
|
21
|
+
if origin is Annotated:
|
|
22
|
+
args = get_args(ann)
|
|
23
|
+
base = args[0] if args else Any
|
|
24
|
+
metas = args[1:] if len(args) > 1 else ()
|
|
25
|
+
for m in metas:
|
|
26
|
+
if isinstance(m, Qualifier):
|
|
27
|
+
qualifier = str(m)
|
|
28
|
+
break
|
|
29
|
+
return base, qualifier
|
|
30
|
+
|
|
31
|
+
def _check_optional(ann: Any) -> Tuple[Any, bool]:
|
|
32
|
+
origin = get_origin(ann)
|
|
33
|
+
if origin is Union:
|
|
34
|
+
args = [a for a in get_args(ann) if a is not type(None)]
|
|
35
|
+
if len(args) == 1:
|
|
36
|
+
return args[0], True
|
|
37
|
+
return ann, False
|
|
38
|
+
|
|
39
|
+
def analyze_callable_dependencies(callable_obj: Callable[..., Any]) -> Tuple[DependencyRequest, ...]:
|
|
40
|
+
try:
|
|
41
|
+
sig = inspect.signature(callable_obj)
|
|
42
|
+
except (ValueError, TypeError):
|
|
43
|
+
return ()
|
|
44
|
+
|
|
45
|
+
plan: List[DependencyRequest] = []
|
|
46
|
+
|
|
47
|
+
for name, param in sig.parameters.items():
|
|
48
|
+
if name in ("self", "cls"):
|
|
49
|
+
continue
|
|
50
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
ann = param.annotation
|
|
54
|
+
|
|
55
|
+
base_type, is_optional = _check_optional(ann)
|
|
56
|
+
base_type, qualifier = _extract_annotated(base_type)
|
|
57
|
+
|
|
58
|
+
is_list = False
|
|
59
|
+
elem_t = None
|
|
60
|
+
|
|
61
|
+
origin = get_origin(base_type)
|
|
62
|
+
if origin in (list, List):
|
|
63
|
+
is_list = True
|
|
64
|
+
elem_t = get_args(base_type)[0] if get_args(base_type) else Any
|
|
65
|
+
elem_t, list_qualifier = _extract_annotated(elem_t)
|
|
66
|
+
if qualifier is None:
|
|
67
|
+
qualifier = list_qualifier
|
|
68
|
+
|
|
69
|
+
final_key: KeyT
|
|
70
|
+
if is_list:
|
|
71
|
+
final_key = elem_t if isinstance(elem_t, type) else Any
|
|
72
|
+
elif isinstance(base_type, type):
|
|
73
|
+
final_key = base_type
|
|
74
|
+
elif isinstance(base_type, str):
|
|
75
|
+
final_key = base_type
|
|
76
|
+
elif ann is inspect._empty:
|
|
77
|
+
final_key = name
|
|
78
|
+
else:
|
|
79
|
+
final_key = base_type
|
|
80
|
+
|
|
81
|
+
plan.append(
|
|
82
|
+
DependencyRequest(
|
|
83
|
+
parameter_name=name,
|
|
84
|
+
key=final_key,
|
|
85
|
+
is_list=is_list,
|
|
86
|
+
qualifier=qualifier,
|
|
87
|
+
is_optional=is_optional or (param.default is not inspect._empty)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return tuple(plan)
|
|
92
|
+
|
pico_ioc/aop.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# src/pico_ioc/aop.py
|
|
2
1
|
import inspect
|
|
3
2
|
import pickle
|
|
4
3
|
import threading
|
|
@@ -148,6 +147,7 @@ class UnifiedComponentProxy:
|
|
|
148
147
|
original_func = bound
|
|
149
148
|
if hasattr(bound, '__func__'):
|
|
150
149
|
original_func = bound.__func__
|
|
150
|
+
|
|
151
151
|
if inspect.iscoroutinefunction(original_func):
|
|
152
152
|
async def aw(*args, **kwargs):
|
|
153
153
|
ctx = MethodCtx(
|
|
@@ -192,6 +192,7 @@ class UnifiedComponentProxy:
|
|
|
192
192
|
attr = getattr(target, name)
|
|
193
193
|
if not callable(attr):
|
|
194
194
|
return attr
|
|
195
|
+
|
|
195
196
|
interceptors_cls = _gather_interceptors_for_method(type(target), name)
|
|
196
197
|
if not interceptors_cls:
|
|
197
198
|
return attr
|
|
@@ -201,16 +202,12 @@ class UnifiedComponentProxy:
|
|
|
201
202
|
cache: Dict[str, Tuple[Tuple[Any, ...], Callable[..., Any], Tuple[type, ...]]] = object.__getattribute__(self, "_cache")
|
|
202
203
|
cur_sig = self._scope_signature()
|
|
203
204
|
cached = cache.get(name)
|
|
205
|
+
|
|
204
206
|
if cached is not None:
|
|
205
207
|
sig, wrapped, cls_tuple = cached
|
|
206
208
|
if sig == cur_sig and cls_tuple == interceptors_cls:
|
|
207
209
|
return wrapped
|
|
208
210
|
|
|
209
|
-
cached = cache.get(name)
|
|
210
|
-
if cached is not None:
|
|
211
|
-
sig, wrapped, cls_tuple = cached
|
|
212
|
-
if sig == cur_sig and cls_tuple == interceptors_cls:
|
|
213
|
-
return wrapped
|
|
214
211
|
sig, wrapped, cls_tuple = self._build_wrapped(name, attr, interceptors_cls)
|
|
215
212
|
cache[name] = (sig, wrapped, cls_tuple)
|
|
216
213
|
return wrapped
|