11"""Dependency injection using dynamic import and discovery of implementations and subclasses."""
22
3+ from __future__ import annotations
4+
35import importlib
46import pkgutil
5- from collections .abc import Callable
67from functools import lru_cache
78from importlib .metadata import entry_points
89from inspect import isclass
9- from typing import Any
10+ from typing import TYPE_CHECKING , Any
11+
12+ from aignostics_foundry_core .foundry import get_context
13+
14+ if TYPE_CHECKING :
15+ from collections .abc import Callable
16+
17+ from aignostics_foundry_core .foundry import FoundryContext
1018
1119_implementation_cache : dict [tuple [Any , str ], list [Any ]] = {}
1220_subclass_cache : dict [tuple [Any , str ], list [Any ]] = {}
@@ -32,15 +40,19 @@ def discover_plugin_packages() -> tuple[str, ...]:
3240 return tuple (ep .value for ep in eps )
3341
3442
35- def load_modules (project_name : str ) -> None :
36- """Import all top-level submodules of the given project package.
43+ def load_modules (* , context : FoundryContext | None = None ) -> None :
44+ """Import all top-level submodules of the configured project package.
3745
3846 Args:
39- project_name: The importable package name to scan (e.g. ``"bridge"``).
47+ context: Project context supplying the package name. When ``None``,
48+ the global context installed via :func:`aignostics_foundry_core.foundry.set_context`
49+ is used; :func:`~aignostics_foundry_core.foundry.get_context` raises
50+ ``RuntimeError`` if no context has been configured.
4051 """
41- package = importlib .import_module (project_name )
52+ ctx = context or get_context ()
53+ package = importlib .import_module (ctx .name )
4254 for _ , name , _ in pkgutil .iter_modules (package .__path__ ):
43- importlib .import_module (f"{ project_name } .{ name } " )
55+ importlib .import_module (f"{ ctx . name } .{ name } " )
4456
4557
4658def _scan_packages_deep (
@@ -117,27 +129,29 @@ def _scan_packages_shallow(
117129 return results
118130
119131
120- def locate_implementations (_class : type [Any ], project_name : str ) -> list [Any ]:
132+ def locate_implementations (_class : type [Any ], * , context : FoundryContext | None = None ) -> list [Any ]:
121133 """Dynamically discover all instances of some class.
122134
123135 Searches plugin top-level exports first (shallow scan), then deep-scans all
124136 submodules of the main project package. Plugins are registered via entry
125137 points; only their top-level ``__init__.py`` exports are examined (submodules
126138 are not walked). The main package retains full deep-scan behaviour.
127139
128- Cache keys include *project_name* to avoid cross-project cache pollution when
129- multiple projects share this library.
140+ Cache keys include the context name to avoid cross-project cache pollution
141+ when multiple projects share this library.
130142
131143 Args:
132144 _class: Class to search for.
133- project_name: Importable package name of the calling project
134- (e.g. ``"bridge"``). Used as the deep-scan root and as part of the
135- cache key.
145+ context: Project context supplying the package name. When ``None``,
146+ the global context installed via :func:`aignostics_foundry_core.foundry.set_context`
147+ is used; :func:`~aignostics_foundry_core.foundry.get_context` raises
148+ ``RuntimeError`` if no context has been configured.
136149
137150 Returns:
138151 List of discovered instances of the given class.
139152 """
140- cache_key = (_class , project_name )
153+ ctx = context or get_context ()
154+ cache_key = (_class , ctx .name )
141155 if cache_key in _implementation_cache :
142156 return _implementation_cache [cache_key ]
143157
@@ -146,33 +160,35 @@ def predicate(member: object) -> bool:
146160
147161 results = [
148162 * _scan_packages_shallow (discover_plugin_packages (), predicate ),
149- * _scan_packages_deep (project_name , predicate ),
163+ * _scan_packages_deep (ctx . name , predicate ),
150164 ]
151165 _implementation_cache [cache_key ] = results
152166 return results
153167
154168
155- def locate_subclasses (_class : type [Any ], project_name : str ) -> list [Any ]:
169+ def locate_subclasses (_class : type [Any ], * , context : FoundryContext | None = None ) -> list [Any ]:
156170 """Dynamically discover all classes that are subclasses of some type.
157171
158172 Searches plugin top-level exports first (shallow scan), then deep-scans all
159173 submodules of the main project package. Plugins are registered via entry
160174 points; only their top-level ``__init__.py`` exports are examined (submodules
161175 are not walked). The main package retains full deep-scan behaviour.
162176
163- Cache keys include *project_name* to avoid cross-project cache pollution when
164- multiple projects share this library.
177+ Cache keys include the context name to avoid cross-project cache pollution
178+ when multiple projects share this library.
165179
166180 Args:
167181 _class: Parent class of subclasses to search for.
168- project_name: Importable package name of the calling project
169- (e.g. ``"bridge"``). Used as the deep-scan root and as part of the
170- cache key.
182+ context: Project context supplying the package name. When ``None``,
183+ the global context installed via :func:`aignostics_foundry_core.foundry.set_context`
184+ is used; :func:`~aignostics_foundry_core.foundry.get_context` raises
185+ ``RuntimeError`` if no context has been configured.
171186
172187 Returns:
173188 List of discovered subclasses of the given class.
174189 """
175- cache_key = (_class , project_name )
190+ ctx = context or get_context ()
191+ cache_key = (_class , ctx .name )
176192 if cache_key in _subclass_cache :
177193 return _subclass_cache [cache_key ]
178194
@@ -181,7 +197,7 @@ def predicate(member: object) -> bool:
181197
182198 results = [
183199 * _scan_packages_shallow (discover_plugin_packages (), predicate ),
184- * _scan_packages_deep (project_name , predicate ),
200+ * _scan_packages_deep (ctx . name , predicate ),
185201 ]
186202 _subclass_cache [cache_key ] = results
187203 return results
0 commit comments