pico-ioc 1.3.0__py3-none-any.whl → 1.4.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
@@ -5,6 +5,7 @@ except Exception:
5
5
  __version__ = "0.0.0"
6
6
 
7
7
  from .container import PicoContainer, Binder
8
+ from .scope import ScopedContainer
8
9
  from .decorators import (
9
10
  component, factory_component, provides, plugin,
10
11
  Qualifier, qualifier,
@@ -15,6 +16,10 @@ from .resolver import Resolver
15
16
  from .api import init, reset, scope, container_fingerprint
16
17
  from .proxy import ComponentProxy, IoCProxy
17
18
  from .interceptors import Invocation, MethodInterceptor, ContainerInterceptor
19
+ from .config import (
20
+ config_component, EnvSource, FileSource,
21
+ Env, File, Path, Value,
22
+ )
18
23
 
19
24
  __all__ = [
20
25
  "__version__",
@@ -41,5 +46,13 @@ __all__ = [
41
46
  "conditional",
42
47
  "interceptor",
43
48
  "Resolver",
49
+ "ScopedContainer",
50
+ "config_component",
51
+ "EnvSource",
52
+ "FileSource",
53
+ "Env",
54
+ "File",
55
+ "Path",
56
+ "Value",
44
57
  ]
45
58
 
pico_ioc/_state.py CHANGED
@@ -1,40 +1,75 @@
1
- # pico_ioc/_state.py
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from threading import RLock
2
5
  from contextvars import ContextVar
3
- from typing import Optional
4
6
  from contextlib import contextmanager
7
+ from typing import Optional, TYPE_CHECKING
8
+
9
+ # Type-only import to avoid cycles
10
+ if TYPE_CHECKING:
11
+ from .container import PicoContainer
12
+
13
+
14
+ # ---- Task/process context for the active container ----
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class ContainerContext:
18
+ """Immutable snapshot for the active container state."""
19
+ container: "PicoContainer"
20
+ fingerprint: tuple
21
+ root_name: Optional[str]
22
+
23
+
24
+ # Process-wide fallback (for non-async code) guarded by a lock
25
+ _lock = RLock()
26
+ _current_context: Optional[ContainerContext] = None
27
+
28
+ # Task-local context (for async isolation)
29
+ _ctxvar: ContextVar[Optional[ContainerContext]] = ContextVar("pico_ioc_ctx", default=None)
30
+
31
+
32
+ def get_context() -> Optional[ContainerContext]:
33
+ """Return the current context (task-local first, then process-global)."""
34
+ ctx = _ctxvar.get()
35
+ return ctx if ctx is not None else _current_context
36
+
37
+
38
+ def set_context(ctx: Optional[ContainerContext]) -> None:
39
+ """Atomically set both task-local and process-global context."""
40
+ with _lock:
41
+ _ctxvar.set(ctx)
42
+ globals()["_current_context"] = ctx
43
+
44
+
45
+ # Optional compatibility helpers (only used by legacy API paths)
46
+ def get_fingerprint() -> Optional[tuple]:
47
+ ctx = get_context()
48
+ return ctx.fingerprint if ctx else None
49
+
50
+
51
+ def set_fingerprint(fp: Optional[tuple]) -> None:
52
+ """Compatibility shim: setting None clears the active context."""
53
+ if fp is None:
54
+ set_context(None)
55
+ return
56
+ ctx = get_context()
57
+ if ctx is not None:
58
+ set_context(ContainerContext(container=ctx.container, fingerprint=fp, root_name=ctx.root_name))
59
+
60
+
61
+ # ---- Scan/resolve guards (kept as-is) ----
5
62
 
6
63
  _scanning: ContextVar[bool] = ContextVar("pico_scanning", default=False)
7
64
  _resolving: ContextVar[bool] = ContextVar("pico_resolving", default=False)
8
65
 
9
- _container = None
10
- _root_name: Optional[str] = None
11
- _fingerprint: Optional[tuple] = None
12
- _fp_observed: bool = False
13
66
 
14
67
  @contextmanager
15
68
  def scanning_flag():
16
- """Context manager: mark scanning=True within the block."""
69
+ """Mark scanning=True within the block."""
17
70
  tok = _scanning.set(True)
18
71
  try:
19
72
  yield
20
73
  finally:
21
74
  _scanning.reset(tok)
22
75
 
23
- # ---- fingerprint helpers (public via api) ----
24
- def set_fingerprint(fp: Optional[tuple]) -> None:
25
- global _fingerprint
26
- _fingerprint = fp
27
-
28
- def get_fingerprint() -> Optional[tuple]:
29
- return _fingerprint
30
-
31
- def reset_fp_observed() -> None:
32
- global _fp_observed
33
- _fp_observed = False
34
-
35
- def mark_fp_observed() -> None:
36
- global _fp_observed
37
- _fp_observed = True
38
-
39
- def was_fp_observed() -> bool:
40
- return _fp_observed
pico_ioc/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.3.0'
1
+ __version__ = '1.4.0'
pico_ioc/api.py CHANGED
@@ -1,10 +1,8 @@
1
- # src/pico_ioc/api.py
2
1
  from __future__ import annotations
3
2
 
4
3
  import inspect as _inspect
5
4
  import importlib
6
5
  import logging
7
- import os
8
6
  from types import ModuleType
9
7
  from typing import Callable, Optional, Tuple, Any, Dict, Iterable, Sequence
10
8
 
@@ -12,12 +10,12 @@ from .container import PicoContainer
12
10
  from .plugins import PicoPlugin
13
11
  from . import _state
14
12
  from .builder import PicoContainerBuilder
13
+ from .scope import ScopedContainer
14
+ from .config import ConfigRegistry, ConfigSource
15
+
15
16
 
16
- # The only helpers left are those directly related to the public API signature or fingerprinting
17
17
  def reset() -> None:
18
- _state._container = None
19
- _state._root_name = None
20
- _state.set_fingerprint(None)
18
+ _state.set_context(None)
21
19
 
22
20
  def _combine_excludes(a: Optional[Callable[[str], bool]], b: Optional[Callable[[str], bool]]):
23
21
  if not a and not b: return None
@@ -82,6 +80,20 @@ def _make_fingerprint_from_signature(locals_in_init: dict) -> tuple:
82
80
  val = _callable_id(val) if val else None
83
81
  elif name == "overrides":
84
82
  val = _normalize_overrides_for_fp(val)
83
+ elif name == "config":
84
+ cfg = locals_in_init.get("config") or ()
85
+ norm = []
86
+ for s in cfg:
87
+ try:
88
+ if type(s).__name__ == "EnvSource":
89
+ norm.append(("env", getattr(s, "prefix", "")))
90
+ elif type(s).__name__ == "FileSource":
91
+ norm.append(("file", str(getattr(s, "path", ""))))
92
+ else:
93
+ norm.append((type(s).__module__, type(s).__qualname__))
94
+ except Exception:
95
+ norm.append(repr(s))
96
+ val = tuple(norm)
85
97
  else:
86
98
  val = _normalize_for_fp(val)
87
99
  entries.append((name, val))
@@ -89,8 +101,9 @@ def _make_fingerprint_from_signature(locals_in_init: dict) -> tuple:
89
101
 
90
102
  # -------- container reuse and caller exclusion helpers --------
91
103
  def _maybe_reuse_existing(fp: tuple, overrides: Optional[Dict[Any, Any]]) -> Optional[PicoContainer]:
92
- if _state.get_fingerprint() == fp:
93
- return _state._container
104
+ ctx = _state.get_context()
105
+ if ctx and ctx.fingerprint == fp:
106
+ return ctx.container
94
107
  return None
95
108
 
96
109
  def _build_exclude(
@@ -122,6 +135,7 @@ def init(
122
135
  auto_exclude_caller: bool = True, plugins: Tuple[PicoPlugin, ...] = (), reuse: bool = True,
123
136
  overrides: Optional[Dict[Any, Any]] = None, auto_scan: Sequence[str] = (),
124
137
  auto_scan_exclude: Optional[Callable[[str], bool]] = None, strict_autoscan: bool = False,
138
+ config: Sequence[ConfigSource] = (),
125
139
  ) -> PicoContainer:
126
140
  root_name = root_package if isinstance(root_package, str) else getattr(root_package, "__name__", None)
127
141
  fp = _make_fingerprint_from_signature(locals())
@@ -131,7 +145,11 @@ def init(
131
145
  if reused is not None:
132
146
  return reused
133
147
 
134
- builder = PicoContainerBuilder().with_plugins(plugins).with_profiles(profiles).with_overrides(overrides)
148
+ builder = (PicoContainerBuilder()
149
+ .with_plugins(plugins)
150
+ .with_profiles(profiles)
151
+ .with_overrides(overrides)
152
+ .with_config(ConfigRegistry(config or ())))
135
153
 
136
154
  combined_exclude = _build_exclude(exclude, auto_exclude_caller, root_name=root_name)
137
155
  builder.add_scan_package(root_package, exclude=combined_exclude)
@@ -151,9 +169,9 @@ def init(
151
169
 
152
170
  container = builder.build()
153
171
 
154
- _state._container = container
155
- _state._root_name = root_name
156
- _state.set_fingerprint(fp)
172
+ # Activate new context atomically
173
+ new_ctx = _state.ContainerContext(container=container, fingerprint=fp, root_name=root_name)
174
+ _state.set_context(new_ctx)
157
175
  return container
158
176
 
159
177
  def scope(
@@ -178,9 +196,9 @@ def scope(
178
196
  for m in modules:
179
197
  builder.add_scan_package(m)
180
198
 
181
- built_container = builder.build()
199
+ built_container = builder.with_eager(not lazy).build()
182
200
 
183
- scoped_container = _ScopedContainer(base=base, strict=strict, built_container=built_container)
201
+ scoped_container = ScopedContainer(base=base, strict=strict, built_container=built_container)
184
202
 
185
203
  if not lazy:
186
204
  from .proxy import ComponentProxy
@@ -195,46 +213,9 @@ def scope(
195
213
  logging.info("Scope container ready.")
196
214
  return scoped_container
197
215
 
198
- class _ScopedContainer(PicoContainer):
199
- def __init__(self, built_container: PicoContainer, base: Optional[PicoContainer], strict: bool):
200
- super().__init__(providers=getattr(built_container, "_providers", {}).copy())
201
-
202
- self._active_profiles = getattr(built_container, "_active_profiles", ())
203
-
204
- base_method_its = getattr(base, "_method_interceptors", ()) if base else ()
205
- base_container_its = getattr(base, "_container_interceptors", ()) if base else ()
206
-
207
- self._method_interceptors = base_method_its
208
- self._container_interceptors = base_container_its
209
- self._seen_interceptor_types = {type(it) for it in (base_method_its + base_container_its)}
210
-
211
- for it in getattr(built_container, "_method_interceptors", ()):
212
- self.add_method_interceptor(it)
213
- for it in getattr(built_container, "_container_interceptors", ()):
214
- self.add_container_interceptor(it)
215
-
216
- self._base = base
217
- self._strict = strict
218
-
219
- if base:
220
- self._singletons.update(getattr(base, "_singletons", {}))
221
-
222
- def __enter__(self): return self
223
- def __exit__(self, exc_type, exc, tb): return False
224
-
225
- def has(self, key: Any) -> bool:
226
- if super().has(key): return True
227
- if not self._strict and self._base is not None:
228
- return self._base.has(key)
229
- return False
230
-
231
- def get(self, key: Any):
232
- try:
233
- return super().get(key)
234
- except NameError as e:
235
- if not self._strict and self._base is not None and self._base.has(key):
236
- return self._base.get(key)
237
- raise e
216
+
238
217
 
239
218
  def container_fingerprint() -> Optional[tuple]:
240
- return _state.get_fingerprint()
219
+ ctx = _state.get_context()
220
+ return ctx.fingerprint if ctx else None
221
+
pico_ioc/builder.py CHANGED
@@ -1,12 +1,12 @@
1
1
  # src/pico_ioc/builder.py
2
2
  from __future__ import annotations
3
+
3
4
  import inspect as _inspect
4
5
  import logging
5
6
  import os
6
7
  from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple
7
8
  from typing import get_origin, get_args, Annotated
8
9
 
9
- # Add missing imports for interceptor types
10
10
  from .interceptors import MethodInterceptor, ContainerInterceptor
11
11
  from .container import PicoContainer, _is_compatible
12
12
  from .policy import apply_policy, _conditional_active
@@ -14,9 +14,11 @@ from .plugins import PicoPlugin, run_plugin_hook
14
14
  from .scanner import scan_and_configure
15
15
  from .resolver import Resolver, _get_hints
16
16
  from . import _state
17
-
17
+ from .config import ConfigRegistry
18
18
 
19
19
  class PicoContainerBuilder:
20
+ """Configures and builds a PicoContainer. Does not touch global context."""
21
+
20
22
  def __init__(self):
21
23
  self._scan_plan: List[Tuple[Any, Optional[Callable[[str], bool]], Tuple[PicoPlugin, ...]]] = []
22
24
  self._overrides: Dict[Any, Any] = {}
@@ -27,39 +29,54 @@ class PicoContainerBuilder:
27
29
  self._roots: Iterable[type] = ()
28
30
  self._providers: Dict[Any, Dict] = {}
29
31
  self._interceptor_decls: List[Tuple[Any, dict]] = []
32
+ self._eager: bool = True
33
+ self._config_registry: ConfigRegistry | None = None
34
+
35
+ # -------- fluent config --------
36
+
37
+ def with_config(self, registry: ConfigRegistry) -> "PicoContainerBuilder":
38
+ self._config_registry = registry
39
+ return self
30
40
 
31
- def with_plugins(self, plugins: Tuple[PicoPlugin, ...]) -> PicoContainerBuilder:
32
- self._plugins = plugins
41
+ def with_plugins(self, plugins: Tuple[PicoPlugin, ...]) -> "PicoContainerBuilder":
42
+ self._plugins = plugins or ()
33
43
  return self
34
44
 
35
- def with_profiles(self, profiles: Optional[List[str]]) -> PicoContainerBuilder:
45
+ def with_profiles(self, profiles: Optional[List[str]]) -> "PicoContainerBuilder":
36
46
  self._profiles = profiles
37
47
  return self
38
48
 
39
- def add_scan_package(self, package: Any, exclude: Optional[Callable[[str], bool]] = None) -> PicoContainerBuilder:
49
+ def add_scan_package(self, package: Any, exclude: Optional[Callable[[str], bool]] = None) -> "PicoContainerBuilder":
40
50
  self._scan_plan.append((package, exclude, self._plugins))
41
51
  return self
42
52
 
43
- def with_overrides(self, overrides: Optional[Dict[Any, Any]]) -> PicoContainerBuilder:
53
+ def with_overrides(self, overrides: Optional[Dict[Any, Any]]) -> "PicoContainerBuilder":
44
54
  self._overrides = overrides or {}
45
55
  return self
46
56
 
47
- def with_tag_filters(self, include: Optional[set[str]], exclude: Optional[set[str]]) -> PicoContainerBuilder:
57
+ def with_tag_filters(self, include: Optional[set[str]], exclude: Optional[set[str]]) -> "PicoContainerBuilder":
48
58
  self._include_tags = include
49
59
  self._exclude_tags = exclude
50
60
  return self
51
61
 
52
- def with_roots(self, roots: Iterable[type]) -> PicoContainerBuilder:
53
- self._roots = roots
62
+ def with_roots(self, roots: Iterable[type]) -> "PicoContainerBuilder":
63
+ self._roots = roots or ()
54
64
  return self
55
65
 
66
+ def with_eager(self, eager: bool) -> "PicoContainerBuilder":
67
+ self._eager = bool(eager)
68
+ return self
69
+
70
+ # -------- build --------
71
+
56
72
  def build(self) -> PicoContainer:
73
+ """Build and return a fully configured container."""
57
74
  requested_profiles = _resolve_profiles(self._profiles)
58
-
59
- # We now create a single container instance upfront and configure it.
75
+
60
76
  container = PicoContainer(providers=self._providers)
61
77
  container._active_profiles = tuple(requested_profiles)
62
-
78
+ setattr(container, "_config_registry", self._config_registry)
79
+
63
80
  for pkg, exclude, scan_plugins in self._scan_plan:
64
81
  with _state.scanning_flag():
65
82
  c, f, decls = scan_and_configure(pkg, container, exclude=exclude, plugins=scan_plugins)
@@ -69,9 +86,9 @@ class PicoContainerBuilder:
69
86
  _activate_and_build_interceptors(
70
87
  container=container,
71
88
  interceptor_decls=self._interceptor_decls,
72
- profiles=requested_profiles
89
+ profiles=requested_profiles,
73
90
  )
74
-
91
+
75
92
  binder = container.binder()
76
93
 
77
94
  if self._overrides:
@@ -79,24 +96,30 @@ class PicoContainerBuilder:
79
96
 
80
97
  run_plugin_hook(self._plugins, "after_bind", container, binder)
81
98
  run_plugin_hook(self._plugins, "before_eager", container, binder)
99
+
82
100
  apply_policy(container, profiles=requested_profiles)
83
101
  _filter_by_tags(container, self._include_tags, self._exclude_tags)
102
+
84
103
  if self._roots:
85
104
  _restrict_to_subgraph(container, self._roots, self._overrides)
86
105
 
87
106
  run_plugin_hook(self._plugins, "after_ready", container, binder)
88
- container.eager_instantiate_all()
107
+
108
+ if self._eager:
109
+ container.eager_instantiate_all()
89
110
  logging.info("Container configured and ready.")
90
111
  return container
91
112
 
92
- # ... (Helper functions like _resolve_profiles, _apply_overrides etc. remain here) ...
93
- # --- Start of moved helpers ---
94
- def _resolve_profiles(profiles: Optional[list[str]]) -> list[str]:
113
+
114
+ # ---------------- helpers ----------------
115
+
116
+ def _resolve_profiles(profiles: Optional[List[str]]) -> List[str]:
95
117
  if profiles is not None:
96
118
  return list(profiles)
97
119
  env_val = os.getenv("PICO_PROFILE", "")
98
120
  return [p.strip() for p in env_val.split(",") if p.strip()]
99
121
 
122
+
100
123
  def _as_provider(val):
101
124
  if isinstance(val, tuple) and len(val) == 2 and callable(val[0]) and isinstance(val[1], bool):
102
125
  return val[0], val[1]
@@ -104,11 +127,13 @@ def _as_provider(val):
104
127
  return val, False
105
128
  return (lambda v=val: v), False
106
129
 
130
+
107
131
  def _apply_overrides(container: PicoContainer, overrides: Dict[Any, Any]) -> None:
108
132
  for key, val in overrides.items():
109
133
  provider, lazy = _as_provider(val)
110
134
  container.bind(key, provider, lazy=lazy)
111
135
 
136
+
112
137
  def _filter_by_tags(container: PicoContainer, include_tags: Optional[set[str]], exclude_tags: Optional[set[str]]) -> None:
113
138
  if not include_tags and not exclude_tags:
114
139
  return
@@ -120,12 +145,14 @@ def _filter_by_tags(container: PicoContainer, include_tags: Optional[set[str]],
120
145
  if exclude_tags and tags.intersection(exclude_tags):
121
146
  return False
122
147
  return True
148
+
123
149
  container._providers = {k: v for k, v in container._providers.items() if _tag_ok(v)}
124
150
 
151
+
125
152
  def _compute_allowed_subgraph(container: PicoContainer, roots: Iterable[type]) -> set:
126
- allowed: set[Any] = set(roots) # Start with roots
153
+ allowed: set[Any] = set(roots)
127
154
  stack = list(roots or ())
128
- # ... (rest of the function is the same, just ensure it's here)
155
+
129
156
  def _add_impls_for_base(base_t):
130
157
  for prov_key, meta in container._providers.items():
131
158
  cls = prov_key if isinstance(prov_key, type) else None
@@ -136,23 +163,29 @@ def _compute_allowed_subgraph(container: PicoContainer, roots: Iterable[type]) -
136
163
 
137
164
  while stack:
138
165
  k = stack.pop()
139
- # if k in allowed: continue # Redundant, add() handles it
140
166
  allowed.add(k)
141
- if isinstance(k, type): _add_impls_for_base(k)
167
+ if isinstance(k, type):
168
+ _add_impls_for_base(k)
169
+
142
170
  cls = k if isinstance(k, type) else None
143
- if cls is None or not container.has(k): continue
171
+ if cls is None or not container.has(k):
172
+ continue
173
+
144
174
  try:
145
175
  sig = _inspect.signature(cls.__init__)
146
176
  hints = _get_hints(cls.__init__, owner_cls=cls)
147
177
  except Exception:
148
178
  continue
179
+
149
180
  for pname, param in sig.parameters.items():
150
- if pname == "self": continue
181
+ if pname == "self":
182
+ continue
151
183
  ann = hints.get(pname, param.annotation)
152
184
  origin = get_origin(ann) or ann
153
185
  if origin in (list, tuple):
154
186
  inner = (get_args(ann) or (object,))[0]
155
- if get_origin(inner) is Annotated: inner = (get_args(inner) or (object,))[0]
187
+ if get_origin(inner) is Annotated:
188
+ inner = (get_args(inner) or (object,))[0]
156
189
  if isinstance(inner, type):
157
190
  if inner not in allowed:
158
191
  stack.append(inner)
@@ -169,38 +202,46 @@ def _restrict_to_subgraph(container: PicoContainer, roots: Iterable[type], overr
169
202
  keep_keys: set[Any] = allowed | (set(overrides.keys()) if overrides else set())
170
203
  container._providers = {k: v for k, v in container._providers.items() if k in keep_keys}
171
204
 
205
+
172
206
  def _activate_and_build_interceptors(
173
- *, container: PicoContainer, interceptor_decls: list[tuple[Any, dict]], profiles: list[str],
207
+ *, container: PicoContainer, interceptor_decls: List[Tuple[Any, dict]], profiles: List[str],
174
208
  ) -> None:
175
209
  resolver = Resolver(container)
176
- active: list[tuple[int, str, str, Any]] = []
177
- activated_method_names: list[str] = []
178
- activated_container_names: list[str] = []
179
- skipped_debug: list[str] = []
210
+ active: List[Tuple[int, str, str, Any]] = []
211
+ activated_method_names: List[str] = []
212
+ activated_container_names: List[str] = []
213
+ skipped_debug: List[str] = []
180
214
 
181
215
  def _interceptor_meta_active(meta: dict) -> bool:
182
216
  profs = tuple(meta.get("profiles", ())) or ()
183
- if profs and (not profiles or not any(p in profs for p in profiles)): return False
217
+ if profs and (not profiles or not any(p in profs for p in profiles)):
218
+ return False
184
219
  req_env = tuple(meta.get("require_env", ())) or ()
185
- if req_env and not all(os.getenv(k) not in (None, "") for k in req_env): return False
220
+ if req_env and not all(os.getenv(k) not in (None, "") for k in req_env):
221
+ return False
186
222
  pred = meta.get("predicate", None)
187
223
  if callable(pred):
188
224
  try:
189
- if not bool(pred()): return False
225
+ if not bool(pred()):
226
+ return False
190
227
  except Exception:
191
228
  logging.exception("Interceptor predicate failed; skipping")
192
229
  return False
193
230
  return True
194
231
 
195
232
  def _looks_like_container_interceptor(inst: Any) -> bool:
196
- return all(hasattr(inst, m) for m in ("on_resolve", "on_before_create", "on_after_create", "on_exception"))
233
+ return all(
234
+ hasattr(inst, m) for m in ("on_resolve", "on_before_create", "on_after_create", "on_exception")
235
+ )
197
236
 
198
237
  for raw_obj, meta in interceptor_decls:
199
238
  owner_cls, obj = (raw_obj[0], raw_obj[1]) if isinstance(raw_obj, tuple) and len(raw_obj) == 2 else (None, raw_obj)
200
239
  qn = getattr(obj, "__qualname__", repr(obj))
240
+
201
241
  if not _conditional_active(obj, profiles=profiles) or not _interceptor_meta_active(meta):
202
242
  skipped_debug.append(f"skip:{qn}")
203
243
  continue
244
+
204
245
  try:
205
246
  if isinstance(obj, type):
206
247
  inst = resolver.create_instance(obj)
@@ -215,6 +256,7 @@ def _activate_and_build_interceptors(
215
256
  except Exception:
216
257
  logging.exception("Failed to construct interceptor %r", obj)
217
258
  continue
259
+
218
260
  kind = meta.get("kind", "method")
219
261
  if kind == "method" and not callable(inst):
220
262
  logging.error("Interceptor %s is not valid for kind %s; skipping", qn, kind)
@@ -222,21 +264,31 @@ def _activate_and_build_interceptors(
222
264
  if kind == "container" and not _looks_like_container_interceptor(inst):
223
265
  logging.error("Container interceptor %s lacks required methods; skipping", qn)
224
266
  continue
267
+
225
268
  order = int(meta.get("order", 0))
226
269
  active.append((order, qn, kind, inst))
227
-
270
+
228
271
  active.sort(key=lambda t: (t[0], t[1]))
229
-
272
+
230
273
  for _order, _qn, kind, inst in active:
231
274
  if kind == "container":
232
- container.add_container_interceptor(inst)
275
+ container.add_container_interceptor(inst) # type: ignore[arg-type]
233
276
  activated_container_names.append(_qn)
234
277
  else:
235
- container.add_method_interceptor(inst)
278
+ container.add_method_interceptor(inst) # type: ignore[arg-type]
236
279
  activated_method_names.append(_qn)
237
280
 
238
281
  if activated_method_names or activated_container_names:
239
- logging.info("Interceptors activated: method=%d, container=%d", len(activated_method_names), len(activated_container_names))
240
- logging.debug("Activated method=%s; Activated container=%s", ", ".join(activated_method_names) or "-", ", ".join(activated_container_names) or "-")
282
+ logging.info(
283
+ "Interceptors activated: method=%d, container=%d",
284
+ len(activated_method_names),
285
+ len(activated_container_names),
286
+ )
287
+ logging.debug(
288
+ "Activated method=%s; Activated container=%s",
289
+ ", ".join(activated_method_names) or "-",
290
+ ", ".join(activated_container_names) or "-",
291
+ )
241
292
  if skipped_debug:
242
293
  logging.debug("Skipped interceptors: %s", ", ".join(skipped_debug))
294
+