pico-ioc 2.0.5__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.5.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.5.dist-info/RECORD +0 -17
- {pico_ioc-2.0.5.dist-info → pico_ioc-2.1.0.dist-info}/WHEEL +0 -0
- {pico_ioc-2.0.5.dist-info → pico_ioc-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {pico_ioc-2.0.5.dist-info → pico_ioc-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Set
|
|
2
|
+
from .factory import ComponentFactory, ProviderMetadata
|
|
3
|
+
from .locator import ComponentLocator
|
|
4
|
+
from .exceptions import InvalidBindingError
|
|
5
|
+
from .analysis import DependencyRequest
|
|
6
|
+
|
|
7
|
+
KeyT = Union[str, type]
|
|
8
|
+
|
|
9
|
+
def _fmt(k: KeyT) -> str:
|
|
10
|
+
return getattr(k, '__name__', str(k))
|
|
11
|
+
|
|
12
|
+
def _skip_type(t: type) -> bool:
|
|
13
|
+
if t in (str, int, float, bool, bytes):
|
|
14
|
+
return True
|
|
15
|
+
if t is Any:
|
|
16
|
+
return True
|
|
17
|
+
if getattr(t, "_is_protocol", False):
|
|
18
|
+
return True
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
class DependencyValidator:
|
|
22
|
+
def __init__(self, metadata: Dict[KeyT, ProviderMetadata], factory: ComponentFactory, locator: ComponentLocator):
|
|
23
|
+
self._metadata = metadata
|
|
24
|
+
self._factory = factory
|
|
25
|
+
self._locator = locator
|
|
26
|
+
|
|
27
|
+
def _find_md_for_type(self, t: type) -> Optional[ProviderMetadata]:
|
|
28
|
+
cands: List[ProviderMetadata] = []
|
|
29
|
+
for md in self._metadata.values():
|
|
30
|
+
typ = md.provided_type or md.concrete_class
|
|
31
|
+
if not isinstance(typ, type):
|
|
32
|
+
continue
|
|
33
|
+
try:
|
|
34
|
+
if issubclass(typ, t):
|
|
35
|
+
cands.append(md)
|
|
36
|
+
except TypeError:
|
|
37
|
+
pass
|
|
38
|
+
except Exception:
|
|
39
|
+
continue
|
|
40
|
+
if not cands:
|
|
41
|
+
if getattr(t, "_is_protocol", False):
|
|
42
|
+
for md in self._metadata.values():
|
|
43
|
+
typ = md.provided_type or md.concrete_class
|
|
44
|
+
if isinstance(typ, type) and ComponentLocator._implements_protocol(typ, t):
|
|
45
|
+
cands.append(md)
|
|
46
|
+
|
|
47
|
+
if not cands:
|
|
48
|
+
return None
|
|
49
|
+
prim = [m for m in cands if m.primary]
|
|
50
|
+
return prim[0] if prim else cands[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _find_md_for_name(self, name: str) -> Optional[KeyT]:
|
|
54
|
+
return self._locator.find_key_by_name(name)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_bindings(self) -> None:
|
|
58
|
+
errors: List[str] = []
|
|
59
|
+
|
|
60
|
+
for k, md in self._metadata.items():
|
|
61
|
+
if md.infra == "configuration":
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if not md.dependencies and md.infra not in ("configured", "component") and not md.override:
|
|
65
|
+
continue
|
|
66
|
+
if md.infra == "component" and md.concrete_class and md.concrete_class.__init__ is object.__init__:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
loc_name = f"component {_fmt(k)}"
|
|
71
|
+
if md.factory_method:
|
|
72
|
+
loc_name = f"factory method {md.factory_method}"
|
|
73
|
+
|
|
74
|
+
for dep in md.dependencies:
|
|
75
|
+
if dep.is_optional:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if dep.is_list:
|
|
79
|
+
if dep.qualifier:
|
|
80
|
+
if not self._locator.collect_by_type(dep.key, dep.qualifier) and isinstance(dep.key, type) and not _skip_type(dep.key):
|
|
81
|
+
errors.append(f"{_fmt(k)} ({loc_name}) expects List[{_fmt(dep.key)}] with qualifier '{dep.qualifier}' but no matching components exist")
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
dep_key = dep.key
|
|
85
|
+
if isinstance(dep_key, str):
|
|
86
|
+
key_found_by_name = self._find_md_for_name(dep_key)
|
|
87
|
+
directly_bound = dep_key in self._metadata or self._factory.has(dep_key)
|
|
88
|
+
if not key_found_by_name and not directly_bound:
|
|
89
|
+
errors.append(f"{_fmt(k)} ({loc_name}) depends on string key '{dep_key}' which is not bound")
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
if isinstance(dep_key, type) and not _skip_type(dep_key):
|
|
93
|
+
dep_key_found = self._factory.has(dep_key) or dep_key in self._metadata
|
|
94
|
+
if not dep_key_found:
|
|
95
|
+
assignable_md = self._find_md_for_type(dep_key)
|
|
96
|
+
if assignable_md is None:
|
|
97
|
+
by_name_key = self._find_md_for_name(getattr(dep_key, "__name__", ""))
|
|
98
|
+
if by_name_key is None:
|
|
99
|
+
errors.append(f"{_fmt(k)} ({loc_name}) depends on {_fmt(dep_key)} which is not bound")
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if errors:
|
|
103
|
+
raise InvalidBindingError(errors)
|
pico_ioc/exceptions.py
CHANGED
|
@@ -14,21 +14,6 @@ class ProviderNotFoundError(PicoError):
|
|
|
14
14
|
self.key = key
|
|
15
15
|
self.origin = origin
|
|
16
16
|
|
|
17
|
-
class CircularDependencyError(PicoError):
|
|
18
|
-
def __init__(self, chain: Iterable[Any], current: Any, details: str | None = None, hint: str | None = None):
|
|
19
|
-
chain_str = " -> ".join(getattr(k, "__name__", str(k)) for k in chain)
|
|
20
|
-
cur_str = getattr(current, "__name__", str(current))
|
|
21
|
-
base = f"Circular dependency detected: {chain_str} -> {cur_str}"
|
|
22
|
-
if details:
|
|
23
|
-
base += f"\n\n{details}"
|
|
24
|
-
if hint:
|
|
25
|
-
base += f"\n\nHint: {hint}"
|
|
26
|
-
super().__init__(base)
|
|
27
|
-
self.chain = tuple(chain)
|
|
28
|
-
self.current = current
|
|
29
|
-
self.details = details
|
|
30
|
-
self.hint = hint
|
|
31
|
-
|
|
32
17
|
class ComponentCreationError(PicoError):
|
|
33
18
|
def __init__(self, key: Any, cause: Exception):
|
|
34
19
|
k = getattr(key, "__name__", key)
|
|
@@ -84,4 +69,3 @@ class EventBusHandlerError(EventBusError):
|
|
|
84
69
|
self.event_name = event_name
|
|
85
70
|
self.handler_name = handler_name
|
|
86
71
|
self.cause = cause
|
|
87
|
-
|
pico_ioc/factory.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import Any, Callable, Dict, Optional, Set, Tuple, Union
|
|
4
4
|
from .exceptions import ProviderNotFoundError
|
|
5
|
+
from .analysis import DependencyRequest
|
|
5
6
|
|
|
6
7
|
KeyT = Union[str, type]
|
|
7
8
|
Provider = Callable[[], Any]
|
|
@@ -18,6 +19,7 @@ class ProviderMetadata:
|
|
|
18
19
|
lazy: bool
|
|
19
20
|
infra: Optional[str]
|
|
20
21
|
pico_name: Optional[Any]
|
|
22
|
+
dependencies: Tuple[DependencyRequest, ...] = ()
|
|
21
23
|
override: bool = False
|
|
22
24
|
scope: str = "singleton"
|
|
23
25
|
|
|
@@ -45,4 +47,3 @@ class DeferredProvider:
|
|
|
45
47
|
if self._pico is None or self._locator is None:
|
|
46
48
|
raise RuntimeError("DeferredProvider must be attached before use")
|
|
47
49
|
return self._builder(self._pico, self._locator)
|
|
48
|
-
|
pico_ioc/locator.py
CHANGED
|
@@ -1,45 +1,62 @@
|
|
|
1
|
-
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
1
|
+
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union, get_origin, Annotated, get_args
|
|
2
2
|
from .factory import ProviderMetadata
|
|
3
|
+
from .decorators import Qualifier
|
|
4
|
+
import inspect
|
|
5
|
+
from .analysis import DependencyRequest
|
|
3
6
|
|
|
4
7
|
KeyT = Union[str, type]
|
|
5
8
|
|
|
9
|
+
def _get_signature_static(fn):
|
|
10
|
+
return inspect.signature(fn)
|
|
11
|
+
|
|
6
12
|
class ComponentLocator:
|
|
7
13
|
def __init__(self, metadata: Dict[KeyT, ProviderMetadata], indexes: Dict[str, Dict[Any, List[KeyT]]]) -> None:
|
|
8
14
|
self._metadata = metadata
|
|
9
15
|
self._indexes = indexes
|
|
10
16
|
self._candidates: Optional[Set[KeyT]] = None
|
|
17
|
+
|
|
11
18
|
def _ensure(self) -> Set[KeyT]:
|
|
12
19
|
return set(self._metadata.keys()) if self._candidates is None else set(self._candidates)
|
|
20
|
+
|
|
13
21
|
def _select_index(self, name: str, values: Iterable[Any]) -> Set[KeyT]:
|
|
14
22
|
out: Set[KeyT] = set()
|
|
15
23
|
idx = self._indexes.get(name, {})
|
|
16
24
|
for v in values:
|
|
17
25
|
out.update(idx.get(v, []))
|
|
18
26
|
return out
|
|
27
|
+
|
|
19
28
|
def _new(self, candidates: Set[KeyT]) -> "ComponentLocator":
|
|
20
29
|
nl = ComponentLocator(self._metadata, self._indexes)
|
|
21
30
|
nl._candidates = candidates
|
|
22
31
|
return nl
|
|
32
|
+
|
|
23
33
|
def with_index_any(self, name: str, *values: Any) -> "ComponentLocator":
|
|
24
34
|
base = self._ensure()
|
|
25
35
|
sel = self._select_index(name, values)
|
|
26
36
|
return self._new(base & sel)
|
|
37
|
+
|
|
27
38
|
def with_index_all(self, name: str, *values: Any) -> "ComponentLocator":
|
|
28
39
|
base = self._ensure()
|
|
29
40
|
cur = base
|
|
30
41
|
for v in values:
|
|
31
42
|
cur = cur & set(self._indexes.get(name, {}).get(v, []))
|
|
32
43
|
return self._new(cur)
|
|
44
|
+
|
|
33
45
|
def with_qualifier_any(self, *qs: Any) -> "ComponentLocator":
|
|
34
46
|
return self.with_index_any("qualifier", *qs)
|
|
47
|
+
|
|
35
48
|
def primary_only(self) -> "ComponentLocator":
|
|
36
49
|
return self.with_index_any("primary", True)
|
|
50
|
+
|
|
37
51
|
def lazy(self, is_lazy: bool = True) -> "ComponentLocator":
|
|
38
52
|
return self.with_index_any("lazy", True) if is_lazy else self.with_index_any("lazy", False)
|
|
53
|
+
|
|
39
54
|
def infra(self, *names: Any) -> "ComponentLocator":
|
|
40
55
|
return self.with_index_any("infra", *names)
|
|
56
|
+
|
|
41
57
|
def pico_name(self, *names: Any) -> "ComponentLocator":
|
|
42
58
|
return self.with_index_any("pico_name", *names)
|
|
59
|
+
|
|
43
60
|
def by_key_type(self, t: type) -> "ComponentLocator":
|
|
44
61
|
base = self._ensure()
|
|
45
62
|
if t is str:
|
|
@@ -49,5 +66,66 @@ class ComponentLocator:
|
|
|
49
66
|
else:
|
|
50
67
|
c = {k for k in base if isinstance(k, t)}
|
|
51
68
|
return self._new(c)
|
|
69
|
+
|
|
52
70
|
def keys(self) -> List[KeyT]:
|
|
53
|
-
return list(self._ensure())
|
|
71
|
+
return list(self._ensure())
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _implements_protocol(typ: type, proto: type) -> bool:
|
|
75
|
+
if not getattr(proto, "_is_protocol", False):
|
|
76
|
+
return False
|
|
77
|
+
try:
|
|
78
|
+
if getattr(proto, "__runtime_protocol__", False) or getattr(proto, "__annotations__", None) is not None:
|
|
79
|
+
inst = object.__new__(typ)
|
|
80
|
+
return isinstance(inst, proto)
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
for name, val in proto.__dict__.items():
|
|
84
|
+
if name.startswith("_") or not callable(val):
|
|
85
|
+
continue
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
def collect_by_type(self, t: type, q: Optional[str]) -> List[KeyT]:
|
|
89
|
+
keys = list(self._metadata.keys())
|
|
90
|
+
out: List[KeyT] = []
|
|
91
|
+
for k in keys:
|
|
92
|
+
md = self._metadata.get(k)
|
|
93
|
+
if md is None:
|
|
94
|
+
continue
|
|
95
|
+
typ = md.provided_type or md.concrete_class
|
|
96
|
+
if not isinstance(typ, type):
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
ok = False
|
|
100
|
+
try:
|
|
101
|
+
ok = issubclass(typ, t)
|
|
102
|
+
except Exception:
|
|
103
|
+
ok = ComponentLocator._implements_protocol(typ, t)
|
|
104
|
+
|
|
105
|
+
if ok and (q is None or q in md.qualifiers):
|
|
106
|
+
out.append(k)
|
|
107
|
+
return out
|
|
108
|
+
|
|
109
|
+
def find_key_by_name(self, name: str) -> Optional[KeyT]:
|
|
110
|
+
for k, md in self._metadata.items():
|
|
111
|
+
if md.pico_name == name:
|
|
112
|
+
return k
|
|
113
|
+
typ = md.provided_type or md.concrete_class
|
|
114
|
+
if isinstance(typ, type) and getattr(typ, "__name__", "") == name:
|
|
115
|
+
return k
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def _compile_argplan_static(self, callable_obj):
|
|
119
|
+
raise NotImplementedError("This method is obsolete and replaced by analysis module")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def dependency_keys_for_static(self, md: ProviderMetadata):
|
|
123
|
+
deps: List[KeyT] = []
|
|
124
|
+
for dep in md.dependencies:
|
|
125
|
+
if dep.is_list:
|
|
126
|
+
if isinstance(dep.key, type):
|
|
127
|
+
keys = self.collect_by_type(dep.key, dep.qualifier)
|
|
128
|
+
deps.extend(keys)
|
|
129
|
+
else:
|
|
130
|
+
deps.append(dep.key)
|
|
131
|
+
return tuple(deps)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple, Union
|
|
2
|
+
from .factory import Provider, ProviderMetadata
|
|
3
|
+
from .config_registrar import ConfigurationManager
|
|
4
|
+
|
|
5
|
+
KeyT = Union[str, type]
|
|
6
|
+
|
|
7
|
+
class ProviderSelector:
|
|
8
|
+
def __init__(self, config_manager: ConfigurationManager):
|
|
9
|
+
self._config_manager = config_manager
|
|
10
|
+
|
|
11
|
+
def _rank_provider(self, item: Tuple[bool, Provider, ProviderMetadata]) -> Tuple[int, int, int]:
|
|
12
|
+
provider, md = item[1], item[2]
|
|
13
|
+
|
|
14
|
+
is_present = 1 if self._config_manager.prefix_exists(md) else 0
|
|
15
|
+
|
|
16
|
+
pref = str(md.pico_name or "")
|
|
17
|
+
pref_len = len(pref)
|
|
18
|
+
|
|
19
|
+
is_primary = 1 if item[0] else 0
|
|
20
|
+
|
|
21
|
+
return (is_present, pref_len, is_primary)
|
|
22
|
+
|
|
23
|
+
def select_providers(
|
|
24
|
+
self,
|
|
25
|
+
candidates: Dict[KeyT, List[Tuple[bool, Provider, ProviderMetadata]]],
|
|
26
|
+
) -> Dict[KeyT, Tuple[Provider, ProviderMetadata]]:
|
|
27
|
+
|
|
28
|
+
winners: Dict[KeyT, Tuple[Provider, ProviderMetadata]] = {}
|
|
29
|
+
|
|
30
|
+
for key, lst in candidates.items():
|
|
31
|
+
lst_sorted = sorted(lst, key=self._rank_provider, reverse=True)
|
|
32
|
+
chosen = lst_sorted[0]
|
|
33
|
+
winners[key] = (chosen[1], chosen[2])
|
|
34
|
+
|
|
35
|
+
return winners
|
pico_ioc/registrar.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import functools
|
|
5
|
+
from dataclasses import is_dataclass, fields, MISSING
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, get_args, get_origin, Iterable
|
|
7
|
+
from .constants import LOGGER, PICO_INFRA, PICO_NAME, PICO_KEY, PICO_META
|
|
8
|
+
from .exceptions import ConfigurationError, InvalidBindingError
|
|
9
|
+
from .factory import ComponentFactory, ProviderMetadata, DeferredProvider
|
|
10
|
+
from .locator import ComponentLocator
|
|
11
|
+
from .aop import UnifiedComponentProxy
|
|
12
|
+
from .decorators import Qualifier, get_return_type
|
|
13
|
+
from .config_builder import ContextConfig
|
|
14
|
+
from .config_runtime import TreeSource
|
|
15
|
+
from .config_registrar import ConfigurationManager
|
|
16
|
+
from .provider_selector import ProviderSelector
|
|
17
|
+
from .dependency_validator import DependencyValidator
|
|
18
|
+
from .component_scanner import ComponentScanner
|
|
19
|
+
from .analysis import analyze_callable_dependencies, DependencyRequest
|
|
20
|
+
|
|
21
|
+
KeyT = Union[str, type]
|
|
22
|
+
Provider = Callable[[], Any]
|
|
23
|
+
|
|
24
|
+
def _can_be_selected_for(reg_md: Dict[KeyT, ProviderMetadata], selector: Any) -> bool:
|
|
25
|
+
if not isinstance(selector, type):
|
|
26
|
+
return False
|
|
27
|
+
for md in reg_md.values():
|
|
28
|
+
typ = md.provided_type or md.concrete_class
|
|
29
|
+
if isinstance(typ, type):
|
|
30
|
+
try:
|
|
31
|
+
if issubclass(typ, selector):
|
|
32
|
+
return True
|
|
33
|
+
except Exception:
|
|
34
|
+
continue
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
class Registrar:
|
|
38
|
+
def __init__(self, factory: ComponentFactory, *, profiles: Tuple[str, ...] = (), environ: Optional[Dict[str, str]] = None, logger: Optional[logging.Logger] = None, config: Optional[ContextConfig] = None) -> None:
|
|
39
|
+
self._factory = factory
|
|
40
|
+
self._profiles = set(p.strip() for p in profiles if p)
|
|
41
|
+
self._environ = environ if environ is not None else os.environ
|
|
42
|
+
self._metadata: Dict[KeyT, ProviderMetadata] = {}
|
|
43
|
+
self._indexes: Dict[str, Dict[Any, List[KeyT]]] = {}
|
|
44
|
+
self._log = logger or LOGGER
|
|
45
|
+
self._config_manager = ConfigurationManager(config)
|
|
46
|
+
self._provider_selector = ProviderSelector(self._config_manager)
|
|
47
|
+
self._scanner = ComponentScanner(self._profiles, self._environ, self._config_manager)
|
|
48
|
+
self._deferred: List[DeferredProvider] = []
|
|
49
|
+
self._provides_functions: Dict[KeyT, Callable[..., Any]] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def locator(self) -> ComponentLocator:
|
|
53
|
+
loc = ComponentLocator(dict(self._metadata), dict(self._indexes))
|
|
54
|
+
setattr(loc, "_provides_functions", dict(self._provides_functions))
|
|
55
|
+
return loc
|
|
56
|
+
|
|
57
|
+
def attach_runtime(self, pico, locator: ComponentLocator) -> None:
|
|
58
|
+
for deferred in self._deferred:
|
|
59
|
+
deferred.attach(pico, locator)
|
|
60
|
+
for key, md in list(self._metadata.items()):
|
|
61
|
+
if md.lazy:
|
|
62
|
+
original = self._factory.get(key, origin='lazy')
|
|
63
|
+
def lazy_proxy_provider(_orig=original, _p=pico):
|
|
64
|
+
return UnifiedComponentProxy(container=_p, object_creator=_orig)
|
|
65
|
+
self._factory.bind(key, lazy_proxy_provider)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _bind_if_absent(self, key: KeyT, provider: Provider) -> None:
|
|
69
|
+
if not self._factory.has(key):
|
|
70
|
+
self._factory.bind(key, provider)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def register_module(self, module: Any) -> None:
|
|
74
|
+
self._scanner.scan_module(module)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _find_md_for_type(self, t: type) -> Optional[ProviderMetadata]:
|
|
78
|
+
cands: List[ProviderMetadata] = []
|
|
79
|
+
for md in self._metadata.values():
|
|
80
|
+
typ = md.provided_type or md.concrete_class
|
|
81
|
+
if not isinstance(typ, type):
|
|
82
|
+
continue
|
|
83
|
+
try:
|
|
84
|
+
if issubclass(typ, t):
|
|
85
|
+
cands.append(md)
|
|
86
|
+
except Exception:
|
|
87
|
+
continue
|
|
88
|
+
if not cands:
|
|
89
|
+
return None
|
|
90
|
+
prim = [m for m in cands if m.primary]
|
|
91
|
+
return prim[0] if prim else cands[0]
|
|
92
|
+
|
|
93
|
+
def _find_narrower_scope_from_deps(self, deps: Tuple[DependencyRequest, ...]) -> Optional[str]:
|
|
94
|
+
if not deps:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
for dep_req in deps:
|
|
98
|
+
if dep_req.is_list:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
dep_md = self._metadata.get(dep_req.key)
|
|
102
|
+
if dep_md is None:
|
|
103
|
+
if isinstance(dep_req.key, type):
|
|
104
|
+
dep_md = self._find_md_for_type(dep_req.key)
|
|
105
|
+
|
|
106
|
+
if dep_md and dep_md.scope != "singleton":
|
|
107
|
+
return dep_md.scope
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
def _promote_scopes(self) -> None:
|
|
111
|
+
for k, md in list(self._metadata.items()):
|
|
112
|
+
if md.scope == "singleton":
|
|
113
|
+
ns = self._find_narrower_scope_from_deps(md.dependencies)
|
|
114
|
+
if ns and ns != "singleton":
|
|
115
|
+
self._metadata[k] = ProviderMetadata(key=md.key, provided_type=md.provided_type, concrete_class=md.concrete_class, factory_class=md.factory_class, factory_method=md.factory_method, qualifiers=md.qualifiers, primary=md.primary, lazy=md.lazy, infra=md.infra, pico_name=md.pico_name, override=md.override, scope=ns, dependencies=md.dependencies)
|
|
116
|
+
|
|
117
|
+
def _rebuild_indexes(self) -> None:
|
|
118
|
+
self._indexes.clear()
|
|
119
|
+
def add(idx: str, val: Any, key: KeyT):
|
|
120
|
+
b = self._indexes.setdefault(idx, {}).setdefault(val, [])
|
|
121
|
+
if key not in b:
|
|
122
|
+
b.append(key)
|
|
123
|
+
|
|
124
|
+
for k, md in self._metadata.items():
|
|
125
|
+
for q in md.qualifiers:
|
|
126
|
+
add("qualifier", q, k)
|
|
127
|
+
if md.primary:
|
|
128
|
+
add("primary", True, k)
|
|
129
|
+
add("lazy", bool(md.lazy), k)
|
|
130
|
+
if md.infra is not None:
|
|
131
|
+
add("infra", md.infra, k)
|
|
132
|
+
if md.pico_name is not None:
|
|
133
|
+
add("pico_name", md.pico_name, k)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def finalize(self, overrides: Optional[Dict[KeyT, Any]]) -> None:
|
|
137
|
+
candidates, on_missing, deferred_providers, provides_functions = self._scanner.get_scan_results()
|
|
138
|
+
self._deferred = deferred_providers
|
|
139
|
+
self._provides_functions = provides_functions
|
|
140
|
+
|
|
141
|
+
winners = self._provider_selector.select_providers(candidates)
|
|
142
|
+
for key, (provider, md) in winners.items():
|
|
143
|
+
self._bind_if_absent(key, provider)
|
|
144
|
+
self._metadata[key] = md
|
|
145
|
+
|
|
146
|
+
self._promote_scopes()
|
|
147
|
+
self._rebuild_indexes()
|
|
148
|
+
|
|
149
|
+
for _, selector, default_cls in sorted(on_missing, key=lambda x: -x[0]):
|
|
150
|
+
key = selector
|
|
151
|
+
if key in self._metadata or self._factory.has(key) or _can_be_selected_for(self._metadata, selector):
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
deps = analyze_callable_dependencies(default_cls.__init__)
|
|
155
|
+
provider = DeferredProvider(lambda pico, loc, c=default_cls, d=deps: pico.build_class(c, loc, d))
|
|
156
|
+
qset = set(str(q) for q in getattr(default_cls, PICO_META, {}).get("qualifier", ()))
|
|
157
|
+
sc = getattr(default_cls, PICO_META, {}).get("scope", "singleton")
|
|
158
|
+
md = ProviderMetadata(key=key, provided_type=key if isinstance(key, type) else None, concrete_class=default_cls, factory_class=None, factory_method=None, qualifiers=qset, primary=True, lazy=bool(getattr(default_cls, PICO_META, {}).get("lazy", False)), infra=getattr(default_cls, PICO_INFRA, None), pico_name=getattr(default_cls, PICO_NAME, None), override=True, scope=sc, dependencies=deps)
|
|
159
|
+
|
|
160
|
+
self._bind_if_absent(key, provider)
|
|
161
|
+
self._metadata[key] = md
|
|
162
|
+
if isinstance(provider, DeferredProvider):
|
|
163
|
+
self._deferred.append(provider)
|
|
164
|
+
|
|
165
|
+
self._rebuild_indexes()
|
|
166
|
+
|
|
167
|
+
final_locator = ComponentLocator(self._metadata, self._indexes)
|
|
168
|
+
validator = DependencyValidator(self._metadata, self._factory, final_locator)
|
|
169
|
+
validator.validate_bindings()
|