Coverage for fastblocks/actions/gather/application.py: 54%
247 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
1"""Application component gathering and initialization orchestration."""
3import typing as t
4from importlib import import_module
6from acb.adapters import get_adapters
7from acb.debug import debug
9from .strategies import GatherStrategy, gather_with_strategy
12class ApplicationGatherResult:
13 def __init__(
14 self,
15 *,
16 adapters: dict[str, t.Any] | None = None,
17 acb_modules: dict[str, t.Any] | None = None,
18 dependencies: dict[str, t.Any] | None = None,
19 initializers: list[t.Callable[..., t.Any]] | None = None,
20 config: t.Any | None = None,
21 errors: list[Exception] | None = None,
22 ) -> None:
23 self.adapters = adapters if adapters is not None else {}
24 self.acb_modules = acb_modules if acb_modules is not None else {}
25 self.dependencies = dependencies if dependencies is not None else {}
26 self.initializers = initializers if initializers is not None else []
27 self.config = config
28 self.errors = errors if errors is not None else []
30 @property
31 def total_components(self) -> int:
32 return (
33 len(self.adapters)
34 + len(self.acb_modules)
35 + len(self.dependencies)
36 + len(self.initializers)
37 )
39 @property
40 def has_errors(self) -> bool:
41 return len(self.errors) > 0
44async def gather_application(
45 *,
46 include_adapters: bool = True,
47 include_acb_modules: bool = True,
48 include_dependencies: bool = True,
49 include_initializers: bool = True,
50 adapter_patterns: list[str] | None = None,
51 dependency_patterns: list[str] | None = None,
52 strategy: GatherStrategy | None = None,
53) -> ApplicationGatherResult:
54 config = _prepare_application_gather_config(
55 adapter_patterns,
56 dependency_patterns,
57 strategy,
58 )
59 result = ApplicationGatherResult()
61 tasks = _build_application_gather_tasks(
62 config,
63 include_adapters,
64 include_acb_modules,
65 include_dependencies,
66 include_initializers,
67 )
69 gather_result = await gather_with_strategy(
70 tasks,
71 config["strategy"],
72 cache_key=f"application:{include_adapters}:{include_acb_modules}:{':'.join(config['dependency_patterns'])}",
73 )
75 _process_application_gather_results(
76 gather_result,
77 result,
78 include_adapters,
79 include_acb_modules,
80 include_dependencies,
81 include_initializers,
82 )
84 await _gather_application_config(result)
86 result.errors.extend(gather_result.errors)
87 debug(f"Gathered {result.total_components} application components")
89 return result
92def _prepare_application_gather_config(
93 adapter_patterns: list[str] | None,
94 dependency_patterns: list[str] | None,
95 strategy: GatherStrategy | None,
96) -> dict[str, t.Any]:
97 return {
98 "adapter_patterns": adapter_patterns
99 or ["__init__.py", "models.py", "views.py"],
100 "dependency_patterns": dependency_patterns
101 or ["models", "config", "cache", "database"],
102 "strategy": strategy or GatherStrategy(),
103 }
106def _build_application_gather_tasks(
107 config: dict[str, t.Any],
108 include_adapters: bool,
109 include_acb_modules: bool,
110 include_dependencies: bool,
111 include_initializers: bool,
112) -> list[t.Coroutine[t.Any, t.Any, t.Any]]:
113 tasks: list[t.Coroutine[t.Any, t.Any, t.Any]] = []
115 if include_adapters:
116 tasks.append(_gather_adapters_and_modules(config["adapter_patterns"]))
117 if include_acb_modules:
118 tasks.append(_gather_acb_modules())
119 if include_dependencies:
120 tasks.append(_gather_application_dependencies(config["dependency_patterns"]))
121 if include_initializers:
122 tasks.append(_gather_initializers())
124 return tasks
127def _process_application_gather_results(
128 gather_result: t.Any,
129 result: ApplicationGatherResult,
130 include_adapters: bool,
131 include_acb_modules: bool,
132 include_dependencies: bool,
133 include_initializers: bool,
134) -> None:
135 for i, success in enumerate(gather_result.success):
136 task_index = 0
138 if include_adapters and i == task_index:
139 result.adapters = success.get("adapters", {})
140 result.acb_modules.update(success.get("adapter_modules", {}))
141 task_index += 1
142 continue
144 if include_acb_modules and i == task_index:
145 result.acb_modules.update(success)
146 task_index += 1
147 continue
149 if include_dependencies and i == task_index:
150 result.dependencies = success
151 task_index += 1
152 continue
154 if include_initializers and i == task_index:
155 result.initializers = success
158async def _gather_application_config(result: ApplicationGatherResult) -> None:
159 try:
160 result.config = await _gather_config()
161 except Exception as e:
162 result.errors.append(e)
165async def _gather_adapters_and_modules(adapter_patterns: list[str]) -> dict[str, t.Any]:
166 adapters_info: dict[str, t.Any] = {
167 "adapters": {},
168 "adapter_modules": {},
169 }
170 for adapter in get_adapters():
171 adapter_name = adapter.name
172 adapters_info["adapters"][adapter_name] = adapter
173 for pattern in adapter_patterns:
174 try:
175 if pattern == "__init__.py":
176 module_path = f"acb.adapters.{adapter_name}"
177 module = import_module(module_path)
178 adapters_info["adapter_modules"][f"{adapter_name}_init"] = module
179 debug(f"Loaded adapter module: {module_path}")
180 elif pattern.endswith(".py"):
181 module_name = pattern.removesuffix(".py")
182 module_path = f"acb.adapters.{adapter_name}.{module_name}"
183 try:
184 module = import_module(module_path)
185 adapters_info["adapter_modules"][
186 f"{adapter_name}_{module_name}"
187 ] = module
188 debug(f"Loaded adapter module: {module_path}")
189 except ModuleNotFoundError:
190 debug(f"Module {module_path} not found, skipping")
191 continue
192 except Exception as e:
193 debug(f"Error loading {adapter_name}/{pattern}: {e}")
194 debug(
195 f"Gathered {len(adapters_info['adapters'])} adapters and {len(adapters_info['adapter_modules'])} modules",
196 )
197 return adapters_info
200async def _gather_acb_modules() -> dict[str, t.Any]:
201 acb_modules = {}
202 core_modules = [
203 "acb.depends",
204 "acb.config",
205 "acb.debug",
206 "acb.adapters",
207 ]
208 optional_modules = [
209 "acb.storage",
210 "acb.cache",
211 "acb.queue",
212 "acb.testing",
213 ]
214 for module_path in core_modules:
215 try:
216 module = import_module(module_path)
217 module_name = module_path.split(".")[-1]
218 acb_modules[module_name] = module
219 debug(f"Loaded core ACB module: {module_path}")
220 except Exception as e:
221 debug(f"Error loading core ACB module {module_path}: {e}")
222 for module_path in optional_modules:
223 try:
224 module = import_module(module_path)
225 module_name = module_path.split(".")[-1]
226 acb_modules[module_name] = module
227 debug(f"Loaded optional ACB module: {module_path}")
228 except ModuleNotFoundError:
229 debug(f"Optional ACB module {module_path} not available")
230 except Exception as e:
231 debug(f"Error loading optional ACB module {module_path}: {e}")
232 debug(f"Gathered {len(acb_modules)} ACB modules")
233 return acb_modules
236async def _gather_application_dependencies(
237 dependency_patterns: list[str],
238) -> dict[str, t.Any]:
239 dependencies: dict[str, t.Any] = {}
240 try:
241 from acb.depends import depends
243 for dep_name in dependency_patterns:
244 try:
245 dependency = depends.get(dep_name)
246 if dependency is not None:
247 dependencies[dep_name] = dependency
248 debug(f"Gathered dependency: {dep_name}")
249 except Exception as e:
250 debug(f"Could not get dependency {dep_name}: {e}")
251 except Exception as e:
252 debug(f"Error accessing depends system: {e}")
253 app_modules = [
254 "models",
255 "views",
256 "utils",
257 "services",
258 "handlers",
259 ]
260 for module_name in app_modules:
261 try:
262 module = import_module(module_name)
263 dependencies[f"app_{module_name}"] = module
264 debug(f"Loaded application module: {module_name}")
265 except ModuleNotFoundError:
266 continue
267 except Exception as e:
268 debug(f"Error loading application module {module_name}: {e}")
269 debug(f"Gathered {len(dependencies)} application dependencies")
270 return dependencies
273async def _gather_initializers() -> list[t.Callable[..., t.Any]]:
274 initializers: list[t.Callable[..., t.Any]] = []
275 await _gather_standard_initializers(initializers)
276 await _gather_adapter_initializers(initializers)
277 debug(f"Gathered {len(initializers)} initialization functions")
278 return initializers
281async def _gather_standard_initializers(
282 initializers: list[t.Callable[..., t.Any]],
283) -> None:
284 initializer_paths = [
285 "initializers.init_app",
286 "app.init_app",
287 "main.init_app",
288 "startup.init_app",
289 "init.init_app",
290 ]
292 for init_path in initializer_paths:
293 try:
294 module_path, func_name = init_path.rsplit(".", 1)
295 module = import_module(module_path)
296 init_func = getattr(module, func_name)
297 if callable(init_func):
298 initializers.append(init_func)
299 debug(f"Found initializer: {init_path}")
300 except (ModuleNotFoundError, AttributeError):
301 continue
302 except Exception as e:
303 debug(f"Error loading initializer {init_path}: {e}")
306async def _gather_adapter_initializers(
307 initializers: list[t.Callable[..., t.Any]],
308) -> None:
309 for adapter in get_adapters():
310 try:
311 adapter_init_path = f"acb.adapters.{adapter.name}.init"
312 module = import_module(adapter_init_path)
313 _collect_adapter_init_functions(module, adapter.name, initializers)
314 except ModuleNotFoundError:
315 continue
316 except Exception as e:
317 debug(f"Error loading adapter initializer for {adapter.name}: {e}")
320def _collect_adapter_init_functions(
321 module: t.Any,
322 adapter_name: str,
323 initializers: list[t.Callable[..., t.Any]],
324) -> None:
325 for func_name in ("init", "initialize", "setup", "configure"):
326 if hasattr(module, func_name):
327 init_func = getattr(module, func_name)
328 if callable(init_func):
329 initializers.append(init_func)
330 debug(f"Found adapter initializer: {adapter_name}.{func_name}")
333async def _gather_config() -> t.Any:
334 try:
335 from acb.depends import depends
337 config = depends.get("config")
338 if config:
339 debug("Gathered application config from depends")
340 return config
341 except Exception as e:
342 debug(f"Error getting config from depends: {e}")
343 try:
344 from acb.config import Config
346 debug("Gathered application config directly")
347 return Config()
348 except Exception as e:
349 debug(f"Error importing config directly: {e}")
350 raise
353async def initialize_application_components(
354 gather_result: ApplicationGatherResult,
355) -> dict[str, t.Any]:
356 initialization_results: dict[str, t.Any] = {
357 "adapters_initialized": [],
358 "dependencies_set": [],
359 "initializers_run": [],
360 "errors": [],
361 }
363 for adapter_name in gather_result.adapters:
364 try:
365 initialization_results["adapters_initialized"].append(adapter_name)
366 debug(f"Initialized adapter: {adapter_name}")
368 except Exception as e:
369 initialization_results["errors"].append(e)
370 debug(f"Error initializing adapter {adapter_name}: {e}")
372 try:
373 from acb.depends import depends
375 for dep_name, dependency in gather_result.dependencies.items():
376 try:
377 depends.set(dep_name, dependency)
378 initialization_results["dependencies_set"].append(dep_name)
379 debug(f"Set dependency: {dep_name}")
381 except Exception as e:
382 initialization_results["errors"].append(e)
383 debug(f"Error setting dependency {dep_name}: {e}")
385 except Exception as e:
386 initialization_results["errors"].append(e)
387 debug(f"Error accessing depends system: {e}")
389 for i, initializer in enumerate(gather_result.initializers):
390 try:
391 import inspect
393 sig = inspect.signature(initializer)
395 if len(sig.parameters) > 0:
396 result = initializer(gather_result.config)
397 else:
398 result = initializer()
400 if inspect.iscoroutine(result):
401 await result
403 initialization_results["initializers_run"].append(f"initializer_{i}")
404 debug(f"Ran initializer {i}")
406 except Exception as e:
407 initialization_results["errors"].append(e)
408 debug(f"Error running initializer {i}: {e}")
410 debug(
411 f"Initialized {len(initialization_results['adapters_initialized'])} adapters, "
412 f"{len(initialization_results['dependencies_set'])} dependencies, "
413 f"{len(initialization_results['initializers_run'])} initializers",
414 )
416 return initialization_results
419def get_application_info(gather_result: ApplicationGatherResult) -> dict[str, t.Any]:
420 info: dict[str, t.Any] = {
421 "total_components": gather_result.total_components,
422 "adapters": {
423 "count": len(gather_result.adapters),
424 "names": list(gather_result.adapters.keys()),
425 },
426 "acb_modules": {
427 "count": len(gather_result.acb_modules),
428 "modules": list(gather_result.acb_modules.keys()),
429 },
430 "dependencies": {
431 "count": len(gather_result.dependencies),
432 "types": list(gather_result.dependencies.keys()),
433 },
434 "initializers": {
435 "count": len(gather_result.initializers),
436 },
437 "config_available": gather_result.config is not None,
438 "has_errors": gather_result.has_errors,
439 "error_count": len(gather_result.errors),
440 }
441 if gather_result.config:
442 info["config_info"] = {
443 "type": type(gather_result.config).__name__,
444 "deployed": getattr(gather_result.config, "deployed", False),
445 "debug": getattr(gather_result.config, "debug", False),
446 }
448 return info
451async def create_application_manager(
452 gather_result: ApplicationGatherResult,
453) -> t.Any:
454 try:
455 from fastblocks.applications import (
456 ApplicationManager, # type: ignore[attr-defined]
457 )
458 except ImportError:
460 class SimpleApplicationManager:
461 def __init__(self, gather_result: ApplicationGatherResult) -> None:
462 self.adapters = gather_result.adapters
463 self.dependencies = gather_result.dependencies
464 self.config = gather_result.config
465 self.initializers = gather_result.initializers
467 return SimpleApplicationManager(gather_result)
469 manager = ApplicationManager()
471 manager._adapters = gather_result.adapters
472 manager._dependencies = gather_result.dependencies
473 manager._config = gather_result.config
474 manager._initializers = gather_result.initializers
476 debug(
477 f"Created application manager with {gather_result.total_components} components",
478 )
480 return manager