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 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.4'
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