Coverage for fastblocks/actions/gather/application.py: 54%

247 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-21 04:50 -0700

1"""Application component gathering and initialization orchestration.""" 

2 

3import typing as t 

4from importlib import import_module 

5 

6from acb.adapters import get_adapters 

7from acb.debug import debug 

8 

9from .strategies import GatherStrategy, gather_with_strategy 

10 

11 

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 [] 

29 

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 ) 

38 

39 @property 

40 def has_errors(self) -> bool: 

41 return len(self.errors) > 0 

42 

43 

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() 

60 

61 tasks = _build_application_gather_tasks( 

62 config, 

63 include_adapters, 

64 include_acb_modules, 

65 include_dependencies, 

66 include_initializers, 

67 ) 

68 

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 ) 

74 

75 _process_application_gather_results( 

76 gather_result, 

77 result, 

78 include_adapters, 

79 include_acb_modules, 

80 include_dependencies, 

81 include_initializers, 

82 ) 

83 

84 await _gather_application_config(result) 

85 

86 result.errors.extend(gather_result.errors) 

87 debug(f"Gathered {result.total_components} application components") 

88 

89 return result 

90 

91 

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 } 

104 

105 

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 = [] 

114 

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()) 

123 

124 return tasks 

125 

126 

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 

137 

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 

143 

144 if include_acb_modules and i == task_index: 

145 result.acb_modules.update(success) 

146 task_index += 1 

147 continue 

148 

149 if include_dependencies and i == task_index: 

150 result.dependencies = success 

151 task_index += 1 

152 continue 

153 

154 if include_initializers and i == task_index: 

155 result.initializers = success 

156 

157 

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) 

163 

164 

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 

198 

199 

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 

234 

235 

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 

242 

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 

271 

272 

273async def _gather_initializers() -> list[t.Callable[..., t.Any]]: 

274 initializers = [] 

275 await _gather_standard_initializers(initializers) 

276 await _gather_adapter_initializers(initializers) 

277 debug(f"Gathered {len(initializers)} initialization functions") 

278 return initializers 

279 

280 

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 ] 

291 

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}") 

304 

305 

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}") 

318 

319 

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}") 

331 

332 

333async def _gather_config() -> t.Any: 

334 try: 

335 from acb.depends import depends 

336 

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 

345 

346 debug("Gathered application config directly") 

347 return Config() 

348 except Exception as e: 

349 debug(f"Error importing config directly: {e}") 

350 raise 

351 

352 

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 } 

362 

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}") 

367 

368 except Exception as e: 

369 initialization_results["errors"].append(e) 

370 debug(f"Error initializing adapter {adapter_name}: {e}") 

371 

372 try: 

373 from acb.depends import depends 

374 

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}") 

380 

381 except Exception as e: 

382 initialization_results["errors"].append(e) 

383 debug(f"Error setting dependency {dep_name}: {e}") 

384 

385 except Exception as e: 

386 initialization_results["errors"].append(e) 

387 debug(f"Error accessing depends system: {e}") 

388 

389 for i, initializer in enumerate(gather_result.initializers): 

390 try: 

391 import inspect 

392 

393 sig = inspect.signature(initializer) 

394 

395 if len(sig.parameters) > 0: 

396 result = initializer(gather_result.config) 

397 else: 

398 result = initializer() 

399 

400 if inspect.iscoroutine(result): 

401 await result 

402 

403 initialization_results["initializers_run"].append(f"initializer_{i}") 

404 debug(f"Ran initializer {i}") 

405 

406 except Exception as e: 

407 initialization_results["errors"].append(e) 

408 debug(f"Error running initializer {i}: {e}") 

409 

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 ) 

415 

416 return initialization_results 

417 

418 

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 } 

447 

448 return info 

449 

450 

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: 

459 

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 

466 

467 return SimpleApplicationManager(gather_result) 

468 

469 manager = ApplicationManager() 

470 

471 manager._adapters = gather_result.adapters 

472 manager._dependencies = gather_result.dependencies 

473 manager._config = gather_result.config 

474 manager._initializers = gather_result.initializers 

475 

476 debug( 

477 f"Created application manager with {gather_result.total_components} components", 

478 ) 

479 

480 return manager