application.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988
  1. """
  2. Application Container and Factory System for Trixy
  3. This module provides the central ApplicationContainer class that implements:
  4. - Dependency injection pattern with lazy loading
  5. - Component lifecycle management (initialization, startup, shutdown)
  6. - Thread-safe singleton pattern for different deployment modes
  7. - Integration with event system and configuration system
  8. - Component health monitoring and status reporting
  9. - Graceful shutdown with proper cleanup
  10. The ApplicationContainer serves as the central hub for all Trixy components,
  11. providing easy access to:
  12. - Event handler system
  13. - Configuration manager
  14. - Plugin system
  15. - Satellite manager
  16. - And all other core components
  17. Usage:
  18. from trixy_core.application import ApplicationContainer
  19. # Create application container
  20. application = ApplicationContainer(
  21. mode="server",
  22. config_path="config/server_config.json",
  23. debug_mode=False
  24. )
  25. # Start the application
  26. application.start()
  27. # Access components
  28. event_handler = application.get_event_handler()
  29. plugin_system = application.get_plugin_system()
  30. satellite_manager = application.get_satellite_manager()
  31. config_manager = application.get_config_manager()
  32. # Graceful shutdown
  33. application.shutdown()
  34. """
  35. import os
  36. import sys
  37. import threading
  38. import time
  39. import traceback
  40. import weakref
  41. from typing import Dict, Any, Optional, Set, List, Type, Callable, Union
  42. from pathlib import Path
  43. from dataclasses import dataclass
  44. from enum import Enum
  45. import json
  46. from pprint import pformat
  47. # Import configuration system
  48. try:
  49. from .config import (
  50. ConfigManager,
  51. DeploymentMode,
  52. ServerConfig,
  53. ClientConfig,
  54. StandaloneConfig,
  55. ConfigurationError
  56. )
  57. from .config import pprint as config_pprint
  58. except ImportError as e:
  59. print(f"Failed to import configuration system: {e}")
  60. sys.exit(1)
  61. # Import event system
  62. try:
  63. from .events import (
  64. EventHandler,
  65. TrixyEvent,
  66. SystemStartupEventData,
  67. SystemShutdownEventData,
  68. EventDataFactory
  69. )
  70. from .events import pprint as events_pprint
  71. except ImportError as e:
  72. print(f"Failed to import event system: {e}")
  73. sys.exit(1)
  74. class ComponentStatus(Enum):
  75. """Status of individual components in the application container."""
  76. NOT_INITIALIZED = "not_initialized"
  77. INITIALIZING = "initializing"
  78. INITIALIZED = "initialized"
  79. STARTING = "starting"
  80. RUNNING = "running"
  81. STOPPING = "stopping"
  82. STOPPED = "stopped"
  83. ERROR = "error"
  84. class ApplicationStatus(Enum):
  85. """Overall status of the application container."""
  86. NOT_STARTED = "not_started"
  87. STARTING = "starting"
  88. RUNNING = "running"
  89. SHUTTING_DOWN = "shutting_down"
  90. STOPPED = "stopped"
  91. ERROR = "error"
  92. @dataclass
  93. class ComponentHealth:
  94. """Health information for a component."""
  95. name: str
  96. status: ComponentStatus
  97. last_check: float
  98. errors: List[str]
  99. warnings: List[str]
  100. info: Dict[str, Any]
  101. def is_healthy(self) -> bool:
  102. """Check if component is healthy."""
  103. return self.status in [ComponentStatus.INITIALIZED, ComponentStatus.RUNNING] and len(self.errors) == 0
  104. class ApplicationContainerError(Exception):
  105. """Base exception for application container errors."""
  106. pass
  107. class ComponentInitializationError(ApplicationContainerError):
  108. """Raised when a component fails to initialize."""
  109. pass
  110. class ComponentLifecycleError(ApplicationContainerError):
  111. """Raised when a component lifecycle operation fails."""
  112. pass
  113. def pprint(message: str) -> None:
  114. """
  115. Application container logging function that adapts based on mode.
  116. Uses the same pattern as specified in CLAUDE.md.
  117. """
  118. # For now use simple printing - will integrate with proper logging
  119. print(f"[APPLICATION] {message}")
  120. class ApplicationContainer:
  121. """
  122. Central application container providing dependency injection and lifecycle management.
  123. This is a thread-safe singleton that manages all core components of the Trixy application.
  124. It supports three deployment modes: server, client, and standalone.
  125. Features:
  126. - Lazy component initialization
  127. - Dependency injection
  128. - Component lifecycle management
  129. - Health monitoring
  130. - Graceful shutdown
  131. - Thread-safe access
  132. - Event-driven architecture
  133. """
  134. _instances: Dict[str, 'ApplicationContainer'] = {}
  135. _lock = threading.RLock()
  136. def __init__(
  137. self,
  138. mode: Union[str, DeploymentMode],
  139. config_path: Optional[str] = None,
  140. debug_mode: bool = False,
  141. **kwargs
  142. ):
  143. """
  144. Initialize the application container.
  145. Args:
  146. mode: Deployment mode (server, client, standalone)
  147. config_path: Path to configuration file
  148. debug_mode: Enable debug mode
  149. **kwargs: Additional configuration overrides
  150. """
  151. # Convert string mode to enum
  152. if isinstance(mode, str):
  153. mode = DeploymentMode(mode.lower())
  154. self._mode = mode
  155. self._config_path = config_path
  156. self._debug_mode = debug_mode
  157. self._kwargs = kwargs
  158. # Application state
  159. self._status = ApplicationStatus.NOT_STARTED
  160. self._startup_time: Optional[float] = None
  161. self._shutdown_time: Optional[float] = None
  162. # Component storage
  163. self._components: Dict[str, Any] = {}
  164. self._component_factories: Dict[str, Callable] = {}
  165. self._component_health: Dict[str, ComponentHealth] = {}
  166. self._component_dependencies: Dict[str, Set[str]] = {}
  167. self._component_lock = threading.RLock()
  168. # Core components that will be initialized
  169. self._config_manager: Optional[ConfigManager] = None
  170. self._event_handler: Optional[EventHandler] = None
  171. # Initialize logging
  172. self._init_logging()
  173. pprint(f"ApplicationContainer created for {mode.value} mode")
  174. # Register component factories
  175. self._register_component_factories()
  176. # Store in singleton registry
  177. with self._lock:
  178. instance_key = f"{mode.value}_{id(self)}"
  179. self._instances[instance_key] = self
  180. self._instance_key = instance_key
  181. def _init_logging(self) -> None:
  182. """Initialize logging for the application container."""
  183. # This will be enhanced when integrated with the main logging system
  184. pass
  185. def _register_component_factories(self) -> None:
  186. """Register factory methods for all components."""
  187. self._component_factories = {
  188. 'config_manager': self._create_config_manager,
  189. 'event_handler': self._create_event_handler,
  190. 'plugin_system': self._create_plugin_system,
  191. 'satellite_manager': self._create_satellite_manager,
  192. 'network_manager': self._create_network_manager,
  193. 'asset_manager': self._create_asset_manager,
  194. 'scheduler': self._create_scheduler,
  195. 'conversation_manager': self._create_conversation_manager,
  196. 'arbitration_manager': self._create_arbitration_manager,
  197. }
  198. # Define component dependencies
  199. self._component_dependencies = {
  200. 'config_manager': set(), # No dependencies
  201. 'event_handler': {'config_manager'},
  202. 'plugin_system': {'config_manager', 'event_handler'},
  203. 'satellite_manager': {'config_manager', 'event_handler'},
  204. 'network_manager': {'config_manager', 'event_handler'},
  205. 'asset_manager': {'config_manager'},
  206. 'scheduler': {'config_manager', 'event_handler'},
  207. 'conversation_manager': {'config_manager', 'event_handler'},
  208. 'arbitration_manager': {'config_manager', 'event_handler'},
  209. }
  210. pprint(f"Registered {len(self._component_factories)} component factories")
  211. def start(self) -> None:
  212. """
  213. Start the application container and all components.
  214. This performs:
  215. 1. Configuration loading and validation
  216. 2. Core component initialization
  217. 3. Component startup in dependency order
  218. 4. Event system startup
  219. 5. Application ready signal
  220. Raises:
  221. ApplicationContainerError: If startup fails
  222. """
  223. if self._status != ApplicationStatus.NOT_STARTED:
  224. raise ApplicationContainerError(f"Application already started (status: {self._status.value})")
  225. pprint("Starting application container...")
  226. self._status = ApplicationStatus.STARTING
  227. self._startup_time = time.time()
  228. try:
  229. # Initialize configuration first (it has no dependencies)
  230. config_manager = self.get_config_manager()
  231. pprint("Configuration manager initialized")
  232. # Initialize event handler early (depends on config)
  233. event_handler = self.get_event_handler()
  234. pprint("Event handler initialized")
  235. # Trigger startup event
  236. startup_data = EventDataFactory.create_event_data(
  237. "system_startup",
  238. mode=self._mode.value,
  239. config_file=self._config_path or "default",
  240. debug_mode=self._debug_mode,
  241. startup_time_ms=self._startup_time * 1000
  242. )
  243. event_handler.trigger_event("system_startup", startup_data)
  244. # Initialize other components based on mode
  245. self._initialize_mode_specific_components()
  246. self._status = ApplicationStatus.RUNNING
  247. pprint(f"Application container started successfully in {time.time() - self._startup_time:.2f} seconds")
  248. except Exception as e:
  249. self._status = ApplicationStatus.ERROR
  250. pprint(f"Failed to start application container: {e}")
  251. if self._debug_mode:
  252. pprint(traceback.format_exc())
  253. raise ApplicationContainerError(f"Application startup failed: {e}") from e
  254. def _initialize_mode_specific_components(self) -> None:
  255. """Initialize components specific to the current deployment mode."""
  256. if self._mode == DeploymentMode.SERVER:
  257. pprint("Initializing server mode components...")
  258. self.get_plugin_system()
  259. self.get_satellite_manager()
  260. self.get_network_manager()
  261. self.get_scheduler()
  262. self.get_conversation_manager()
  263. self.get_arbitration_manager()
  264. elif self._mode == DeploymentMode.CLIENT:
  265. pprint("Initializing client mode components...")
  266. self.get_network_manager()
  267. # Client mode has minimal components
  268. elif self._mode == DeploymentMode.STANDALONE:
  269. pprint("Initializing standalone mode components...")
  270. self.get_plugin_system()
  271. self.get_network_manager()
  272. self.get_scheduler()
  273. self.get_conversation_manager()
  274. # Standalone has most components but no satellite manager
  275. # Asset manager is available in all modes
  276. self.get_asset_manager()
  277. def shutdown(self) -> None:
  278. """
  279. Gracefully shutdown the application container.
  280. This performs:
  281. 1. Component shutdown in reverse dependency order
  282. 2. Resource cleanup
  283. 3. Event system shutdown
  284. 4. Final status update
  285. """
  286. if self._status in [ApplicationStatus.SHUTTING_DOWN, ApplicationStatus.STOPPED]:
  287. pprint("Application container already shutting down or stopped")
  288. return
  289. pprint("Shutting down application container...")
  290. self._status = ApplicationStatus.SHUTTING_DOWN
  291. self._shutdown_time = time.time()
  292. try:
  293. # Trigger shutdown event if event handler is available
  294. if self._event_handler:
  295. shutdown_data = EventDataFactory.create_event_data(
  296. "system_shutdown",
  297. reason="normal_shutdown",
  298. graceful_shutdown=True,
  299. uptime_seconds=self._shutdown_time - (self._startup_time or 0)
  300. )
  301. self._event_handler.trigger_event("system_shutdown", shutdown_data)
  302. # Shutdown components in reverse dependency order
  303. shutdown_order = self._get_shutdown_order()
  304. for component_name in shutdown_order:
  305. self._shutdown_component(component_name)
  306. self._status = ApplicationStatus.STOPPED
  307. pprint("Application container shutdown completed")
  308. except Exception as e:
  309. self._status = ApplicationStatus.ERROR
  310. pprint(f"Error during shutdown: {e}")
  311. if self._debug_mode:
  312. pprint(traceback.format_exc())
  313. finally:
  314. # Clean up from singleton registry
  315. with self._lock:
  316. if hasattr(self, '_instance_key') and self._instance_key in self._instances:
  317. del self._instances[self._instance_key]
  318. def _get_shutdown_order(self) -> List[str]:
  319. """Get the order in which components should be shut down (reverse of dependency order)."""
  320. # For now, use a simple reverse order
  321. # This could be enhanced with proper topological sorting
  322. order = [
  323. 'arbitration_manager',
  324. 'conversation_manager',
  325. 'scheduler',
  326. 'asset_manager',
  327. 'network_manager',
  328. 'satellite_manager',
  329. 'plugin_system',
  330. 'event_handler',
  331. 'config_manager'
  332. ]
  333. return [name for name in order if name in self._components]
  334. def _shutdown_component(self, component_name: str) -> None:
  335. """Shutdown a specific component safely."""
  336. if component_name not in self._components:
  337. return
  338. component = self._components[component_name]
  339. try:
  340. pprint(f"Shutting down {component_name}...")
  341. # Call shutdown method if available
  342. if hasattr(component, 'shutdown'):
  343. component.shutdown()
  344. elif hasattr(component, 'stop'):
  345. component.stop()
  346. elif hasattr(component, 'close'):
  347. component.close()
  348. # Update health status
  349. if component_name in self._component_health:
  350. self._component_health[component_name].status = ComponentStatus.STOPPED
  351. pprint(f"{component_name} shutdown completed")
  352. except Exception as e:
  353. pprint(f"Error shutting down {component_name}: {e}")
  354. if self._debug_mode:
  355. pprint(traceback.format_exc())
  356. # Component Factory Methods
  357. def _create_config_manager(self) -> ConfigManager:
  358. """Create and initialize the configuration manager."""
  359. pprint("Creating configuration manager...")
  360. config_manager = ConfigManager(
  361. mode=self._mode,
  362. config_file=self._config_path,
  363. debug_mode=self._debug_mode
  364. )
  365. # Load configuration
  366. config = config_manager.load_config()
  367. pprint(f"Configuration loaded successfully for {self._mode.value} mode")
  368. self._update_component_health('config_manager', ComponentStatus.INITIALIZED)
  369. return config_manager
  370. def _create_event_handler(self) -> EventHandler:
  371. """Create and initialize the event handler."""
  372. pprint("Creating event handler...")
  373. # Get configuration for event handler settings
  374. config_manager = self.get_config_manager()
  375. config = config_manager.get_config()
  376. # Create event handler with configuration
  377. event_handler = EventHandler(
  378. max_history_size=getattr(config.logging, 'max_event_history', 1000)
  379. )
  380. # Enable debug mode if needed
  381. if self._debug_mode:
  382. event_handler.enable_debug_mode(True)
  383. pprint("Event handler created successfully")
  384. self._update_component_health('event_handler', ComponentStatus.INITIALIZED)
  385. return event_handler
  386. def _create_plugin_system(self):
  387. """Create and initialize the plugin system."""
  388. pprint("Creating plugin system...")
  389. try:
  390. # Import the real plugin manager
  391. from .plugins import PluginManager
  392. # Get configuration for plugin system
  393. config_manager = self.get_config_manager()
  394. config = config_manager.get_config()
  395. # Create plugin manager with configuration
  396. plugin_manager = PluginManager(
  397. application=self,
  398. plugins_directory=getattr(config, 'plugins_directory', None),
  399. auto_load=getattr(config, 'auto_load_plugins', True),
  400. enable_hot_reload=self._debug_mode, # Enable hot-reload in debug mode
  401. health_check_interval=getattr(config, 'plugin_health_check_interval', 60.0),
  402. max_error_count=getattr(config, 'plugin_max_error_count', 5)
  403. )
  404. pprint("Plugin manager created successfully")
  405. self._update_component_health('plugin_system', ComponentStatus.INITIALIZED)
  406. return plugin_manager
  407. except ImportError as e:
  408. pprint(f"Failed to import PluginManager: {e}")
  409. # Fall back to placeholder
  410. plugin_system = PluginSystemPlaceholder(self)
  411. self._update_component_health('plugin_system', ComponentStatus.INITIALIZED)
  412. return plugin_system
  413. except Exception as e:
  414. pprint(f"Failed to create plugin manager: {e}")
  415. # Fall back to placeholder on any error
  416. plugin_system = PluginSystemPlaceholder(self)
  417. self._update_component_health('plugin_system', ComponentStatus.ERROR, errors=[str(e)])
  418. return plugin_system
  419. def _create_satellite_manager(self):
  420. """Create and initialize the satellite manager."""
  421. pprint("Creating satellite manager...")
  422. # Satellite manager will be implemented in future
  423. # For now, return a placeholder
  424. satellite_manager = SatelliteManagerPlaceholder(self)
  425. self._update_component_health('satellite_manager', ComponentStatus.INITIALIZED)
  426. return satellite_manager
  427. def _create_network_manager(self):
  428. """Create and initialize the network manager."""
  429. pprint("Creating network manager...")
  430. # Import the actual network manager
  431. try:
  432. from .network import NetworkManager
  433. network_manager = NetworkManager(self)
  434. pprint("Network manager created successfully")
  435. except ImportError as e:
  436. pprint(f"Failed to import NetworkManager: {e}")
  437. # Fall back to placeholder for now
  438. network_manager = NetworkManagerPlaceholder(self)
  439. self._update_component_health('network_manager', ComponentStatus.INITIALIZED)
  440. return network_manager
  441. def _create_asset_manager(self):
  442. """Create and initialize the asset manager."""
  443. pprint("Creating asset manager...")
  444. try:
  445. # Import the real asset manager
  446. from .assets import AssetManager, AssetManagerConfig, ProfileMode
  447. # Get configuration for asset manager
  448. config_manager = self.get_config_manager()
  449. config = config_manager.get_config()
  450. # Determine assets directory and profile from configuration
  451. assets_directory = getattr(config, 'assets_directory', './assets')
  452. asset_profile = getattr(config, 'asset_profile', None)
  453. # Create asset manager configuration
  454. asset_config = AssetManagerConfig(
  455. assets_directory=assets_directory,
  456. current_profile=asset_profile,
  457. cache_enabled=getattr(config, 'asset_cache_enabled', True),
  458. cache_max_size=getattr(config, 'asset_cache_max_size', 100),
  459. cache_max_memory=getattr(config, 'asset_cache_max_memory', 100 * 1024 * 1024),
  460. validation_enabled=getattr(config, 'asset_validation_enabled', True),
  461. hot_reload_enabled=self._debug_mode, # Enable hot-reload in debug mode
  462. hot_reload_debounce=getattr(config, 'asset_hot_reload_debounce', 0.5),
  463. preload_patterns=getattr(config, 'asset_preload_patterns', [
  464. "audio/success.wav",
  465. "audio/error.wav",
  466. "audio/notification.wav",
  467. "audio/wakeword_detected.wav"
  468. ]),
  469. profile_mode=ProfileMode.FALLBACK,
  470. thread_pool_size=getattr(config, 'asset_thread_pool_size', 4),
  471. enable_integrity_checks=getattr(config, 'asset_integrity_checks', True),
  472. auto_create_directories=True,
  473. compress_cached_assets=getattr(config, 'asset_cache_compression', False),
  474. asset_timeout=getattr(config, 'asset_timeout', 30.0)
  475. )
  476. # Create asset manager with configuration
  477. asset_manager = AssetManager(
  478. application_container=self,
  479. config=asset_config
  480. )
  481. pprint("Asset manager created successfully")
  482. self._update_component_health('asset_manager', ComponentStatus.INITIALIZED)
  483. return asset_manager
  484. except ImportError as e:
  485. pprint(f"Failed to import AssetManager: {e}")
  486. # Fall back to placeholder
  487. asset_manager = AssetManagerPlaceholder(self)
  488. self._update_component_health('asset_manager', ComponentStatus.INITIALIZED)
  489. return asset_manager
  490. except Exception as e:
  491. pprint(f"Failed to create asset manager: {e}")
  492. # Fall back to placeholder on any error
  493. asset_manager = AssetManagerPlaceholder(self)
  494. self._update_component_health('asset_manager', ComponentStatus.ERROR, errors=[str(e)])
  495. return asset_manager
  496. def _create_scheduler(self):
  497. """Create and initialize the scheduler."""
  498. pprint("Creating scheduler...")
  499. # Scheduler will be implemented in future
  500. # For now, return a placeholder
  501. scheduler = SchedulerPlaceholder(self)
  502. self._update_component_health('scheduler', ComponentStatus.INITIALIZED)
  503. return scheduler
  504. def _create_conversation_manager(self):
  505. """Create and initialize the conversation manager."""
  506. pprint("Creating conversation manager...")
  507. try:
  508. # Import the real conversation manager
  509. from .conversation import ConversationManager
  510. # Get configuration for conversation manager
  511. config_manager = self.get_config_manager()
  512. config = config_manager.get_config()
  513. # Create conversation manager with configuration
  514. conversation_manager = ConversationManager(
  515. application=self,
  516. max_concurrent_conversations=getattr(config, 'max_concurrent_conversations', 100),
  517. default_conversation_timeout=getattr(config, 'default_conversation_timeout', 300.0),
  518. plugin_question_timeout=getattr(config, 'plugin_question_timeout', 30.0),
  519. cleanup_interval_seconds=getattr(config, 'conversation_cleanup_interval', 60.0),
  520. max_conversation_history=getattr(config, 'max_conversation_history', 1000),
  521. enable_context_persistence=getattr(config, 'enable_conversation_persistence', False),
  522. context_storage_path=getattr(config, 'conversation_storage_path', None),
  523. enable_analytics=getattr(config, 'enable_conversation_analytics', True)
  524. )
  525. pprint("Conversation manager created successfully")
  526. self._update_component_health('conversation_manager', ComponentStatus.INITIALIZED)
  527. return conversation_manager
  528. except ImportError as e:
  529. pprint(f"Failed to import ConversationManager: {e}")
  530. # Fall back to placeholder
  531. conversation_manager = ConversationManagerPlaceholder(self)
  532. self._update_component_health('conversation_manager', ComponentStatus.INITIALIZED)
  533. return conversation_manager
  534. except Exception as e:
  535. pprint(f"Failed to create conversation manager: {e}")
  536. # Fall back to placeholder on any error
  537. conversation_manager = ConversationManagerPlaceholder(self)
  538. self._update_component_health('conversation_manager', ComponentStatus.ERROR, errors=[str(e)])
  539. return conversation_manager
  540. def _create_arbitration_manager(self):
  541. """Create and initialize the arbitration manager."""
  542. pprint("Creating arbitration manager...")
  543. # Arbitration manager will be implemented in future
  544. # For now, return a placeholder
  545. arbitration_manager = ArbitrationManagerPlaceholder(self)
  546. self._update_component_health('arbitration_manager', ComponentStatus.INITIALIZED)
  547. return arbitration_manager
  548. # Component Access Methods (Public API as specified in CLAUDE.md)
  549. def get_config_manager(self) -> ConfigManager:
  550. """Get the configuration manager instance."""
  551. return self._get_component('config_manager')
  552. def get_event_handler(self) -> EventHandler:
  553. """Get the event handler instance."""
  554. return self._get_component('event_handler')
  555. def get_plugin_system(self):
  556. """Get the plugin system instance."""
  557. return self._get_component('plugin_system')
  558. def get_satellite_manager(self):
  559. """Get the satellite manager instance."""
  560. return self._get_component('satellite_manager')
  561. def get_network_manager(self):
  562. """Get the network manager instance."""
  563. return self._get_component('network_manager')
  564. def get_asset_manager(self):
  565. """Get the asset manager instance."""
  566. return self._get_component('asset_manager')
  567. def get_scheduler(self):
  568. """Get the scheduler instance."""
  569. return self._get_component('scheduler')
  570. def get_conversation_manager(self):
  571. """Get the conversation manager instance."""
  572. return self._get_component('conversation_manager')
  573. def get_arbitration_manager(self):
  574. """Get the arbitration manager instance."""
  575. return self._get_component('arbitration_manager')
  576. def _get_component(self, component_name: str):
  577. """
  578. Get a component instance, creating it if necessary (lazy loading).
  579. Args:
  580. component_name: Name of the component
  581. Returns:
  582. Component instance
  583. Raises:
  584. ComponentInitializationError: If component creation fails
  585. """
  586. with self._component_lock:
  587. # Return existing component if available
  588. if component_name in self._components:
  589. return self._components[component_name]
  590. # Check if factory exists
  591. if component_name not in self._component_factories:
  592. raise ComponentInitializationError(f"No factory registered for component: {component_name}")
  593. # Initialize dependencies first
  594. self._ensure_dependencies(component_name)
  595. # Create the component
  596. try:
  597. self._update_component_health(component_name, ComponentStatus.INITIALIZING)
  598. factory = self._component_factories[component_name]
  599. component = factory()
  600. self._components[component_name] = component
  601. self._update_component_health(component_name, ComponentStatus.RUNNING)
  602. pprint(f"Component '{component_name}' initialized successfully")
  603. return component
  604. except Exception as e:
  605. self._update_component_health(component_name, ComponentStatus.ERROR, errors=[str(e)])
  606. raise ComponentInitializationError(f"Failed to initialize {component_name}: {e}") from e
  607. def _ensure_dependencies(self, component_name: str) -> None:
  608. """Ensure all dependencies for a component are initialized."""
  609. if component_name not in self._component_dependencies:
  610. return
  611. dependencies = self._component_dependencies[component_name]
  612. for dep_name in dependencies:
  613. if dep_name not in self._components:
  614. pprint(f"Initializing dependency '{dep_name}' for '{component_name}'")
  615. self._get_component(dep_name)
  616. def _update_component_health(
  617. self,
  618. component_name: str,
  619. status: ComponentStatus,
  620. errors: Optional[List[str]] = None,
  621. warnings: Optional[List[str]] = None,
  622. info: Optional[Dict[str, Any]] = None
  623. ) -> None:
  624. """Update the health status of a component."""
  625. self._component_health[component_name] = ComponentHealth(
  626. name=component_name,
  627. status=status,
  628. last_check=time.time(),
  629. errors=errors or [],
  630. warnings=warnings or [],
  631. info=info or {}
  632. )
  633. # Health and Status Methods
  634. def get_status(self) -> ApplicationStatus:
  635. """Get the current application status."""
  636. return self._status
  637. def get_health_report(self) -> Dict[str, Any]:
  638. """
  639. Get a comprehensive health report for the application and all components.
  640. Returns:
  641. Dictionary containing health information
  642. """
  643. return {
  644. 'application': {
  645. 'status': self._status.value,
  646. 'mode': self._mode.value,
  647. 'uptime': time.time() - (self._startup_time or time.time()),
  648. 'startup_time': self._startup_time,
  649. 'debug_mode': self._debug_mode,
  650. },
  651. 'components': {
  652. name: {
  653. 'status': health.status.value,
  654. 'is_healthy': health.is_healthy(),
  655. 'last_check': health.last_check,
  656. 'errors': health.errors,
  657. 'warnings': health.warnings,
  658. 'info': health.info
  659. }
  660. for name, health in self._component_health.items()
  661. },
  662. 'summary': {
  663. 'total_components': len(self._component_health),
  664. 'healthy_components': sum(1 for h in self._component_health.values() if h.is_healthy()),
  665. 'error_components': sum(1 for h in self._component_health.values() if h.status == ComponentStatus.ERROR),
  666. }
  667. }
  668. def is_healthy(self) -> bool:
  669. """Check if the application and all components are healthy."""
  670. if self._status != ApplicationStatus.RUNNING:
  671. return False
  672. return all(health.is_healthy() for health in self._component_health.values())
  673. # Utility Methods
  674. def get_version(self) -> str:
  675. """Get the application version."""
  676. return "1.0.0"
  677. def get_mode(self) -> DeploymentMode:
  678. """Get the deployment mode."""
  679. return self._mode
  680. def is_debug_mode(self) -> bool:
  681. """Check if debug mode is enabled."""
  682. return self._debug_mode
  683. def get_uptime(self) -> float:
  684. """Get application uptime in seconds."""
  685. if self._startup_time is None:
  686. return 0.0
  687. return time.time() - self._startup_time
  688. @classmethod
  689. def get_instance(cls, mode: Union[str, DeploymentMode] = None) -> Optional['ApplicationContainer']:
  690. """
  691. Get an existing application container instance.
  692. Args:
  693. mode: Deployment mode to find (optional)
  694. Returns:
  695. ApplicationContainer instance or None if not found
  696. """
  697. with cls._lock:
  698. if mode is None:
  699. # Return any available instance
  700. return next(iter(cls._instances.values()), None)
  701. if isinstance(mode, str):
  702. mode = DeploymentMode(mode.lower())
  703. # Find instance with matching mode
  704. for instance in cls._instances.values():
  705. if instance._mode == mode:
  706. return instance
  707. return None
  708. @classmethod
  709. def cleanup_instances(cls) -> None:
  710. """Clean up all application container instances."""
  711. with cls._lock:
  712. for instance in list(cls._instances.values()):
  713. try:
  714. instance.shutdown()
  715. except Exception as e:
  716. pprint(f"Error shutting down instance: {e}")
  717. cls._instances.clear()
  718. # Placeholder classes for components that will be implemented later
  719. # These provide the basic interface expected by the application container
  720. class ComponentPlaceholder:
  721. """Base placeholder for components that haven't been implemented yet."""
  722. def __init__(self, application: ApplicationContainer):
  723. self.application = application
  724. self.application_ref = weakref.ref(application)
  725. def shutdown(self):
  726. """Shutdown method for proper cleanup."""
  727. pass
  728. class PluginSystemPlaceholder(ComponentPlaceholder):
  729. """Placeholder for the plugin system."""
  730. pass
  731. class SatelliteManagerPlaceholder(ComponentPlaceholder):
  732. """Placeholder for the satellite manager."""
  733. pass
  734. class NetworkManagerPlaceholder(ComponentPlaceholder):
  735. """Placeholder for the network manager."""
  736. pass
  737. class AssetManagerPlaceholder(ComponentPlaceholder):
  738. """Placeholder for the asset manager."""
  739. pass
  740. class SchedulerPlaceholder(ComponentPlaceholder):
  741. """Placeholder for the scheduler."""
  742. pass
  743. class ConversationManagerPlaceholder(ComponentPlaceholder):
  744. """Placeholder for the conversation manager."""
  745. pass
  746. class ArbitrationManagerPlaceholder(ComponentPlaceholder):
  747. """Placeholder for the arbitration manager."""
  748. pass
  749. # Utility Functions
  750. def create_application_container(
  751. mode: Union[str, DeploymentMode],
  752. config_path: Optional[str] = None,
  753. debug_mode: bool = False,
  754. **kwargs
  755. ) -> ApplicationContainer:
  756. """
  757. Convenience function to create an ApplicationContainer.
  758. Args:
  759. mode: Deployment mode
  760. config_path: Path to configuration file
  761. debug_mode: Enable debug mode
  762. **kwargs: Additional arguments
  763. Returns:
  764. ApplicationContainer instance
  765. """
  766. return ApplicationContainer(
  767. mode=mode,
  768. config_path=config_path,
  769. debug_mode=debug_mode,
  770. **kwargs
  771. )
  772. def get_application_container(mode: Union[str, DeploymentMode] = None) -> Optional[ApplicationContainer]:
  773. """
  774. Get an existing application container instance.
  775. Args:
  776. mode: Deployment mode (optional)
  777. Returns:
  778. ApplicationContainer instance or None
  779. """
  780. return ApplicationContainer.get_instance(mode)
  781. def cleanup_application_containers() -> None:
  782. """Clean up all application container instances."""
  783. ApplicationContainer.cleanup_instances()
  784. # Module exports
  785. __all__ = [
  786. 'ApplicationContainer',
  787. 'ApplicationContainerError',
  788. 'ComponentInitializationError',
  789. 'ComponentLifecycleError',
  790. 'ApplicationStatus',
  791. 'ComponentStatus',
  792. 'ComponentHealth',
  793. 'create_application_container',
  794. 'get_application_container',
  795. 'cleanup_application_containers',
  796. 'pprint'
  797. ]