application.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  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. 'ml_system': self._create_ml_system,
  198. }
  199. # Define component dependencies
  200. self._component_dependencies = {
  201. 'config_manager': set(), # No dependencies
  202. 'event_handler': {'config_manager'},
  203. 'plugin_system': {'config_manager', 'event_handler'},
  204. 'satellite_manager': {'config_manager', 'event_handler'},
  205. 'network_manager': {'config_manager', 'event_handler'},
  206. 'asset_manager': {'config_manager'},
  207. 'scheduler': {'config_manager', 'event_handler'},
  208. 'conversation_manager': {'config_manager', 'event_handler'},
  209. 'arbitration_manager': {'config_manager', 'event_handler'},
  210. 'ml_system': {'config_manager', 'event_handler'},
  211. }
  212. pprint(f"Registered {len(self._component_factories)} component factories")
  213. def start(self) -> None:
  214. """
  215. Start the application container and all components.
  216. This performs:
  217. 1. Configuration loading and validation
  218. 2. Core component initialization
  219. 3. Component startup in dependency order
  220. 4. Event system startup
  221. 5. Application ready signal
  222. Raises:
  223. ApplicationContainerError: If startup fails
  224. """
  225. if self._status != ApplicationStatus.NOT_STARTED:
  226. raise ApplicationContainerError(f"Application already started (status: {self._status.value})")
  227. pprint("Starting application container...")
  228. self._status = ApplicationStatus.STARTING
  229. self._startup_time = time.time()
  230. try:
  231. # Initialize configuration first (it has no dependencies)
  232. config_manager = self.get_config_manager()
  233. pprint("Configuration manager initialized")
  234. # Initialize event handler early (depends on config)
  235. event_handler = self.get_event_handler()
  236. pprint("Event handler initialized")
  237. # Trigger startup event
  238. startup_data = EventDataFactory.create_event_data(
  239. "system_startup",
  240. mode=self._mode.value,
  241. config_file=self._config_path or "default",
  242. debug_mode=self._debug_mode,
  243. startup_time_ms=self._startup_time * 1000
  244. )
  245. event_handler.trigger_event("system_startup", startup_data)
  246. # Initialize other components based on mode
  247. self._initialize_mode_specific_components()
  248. self._status = ApplicationStatus.RUNNING
  249. pprint(f"Application container started successfully in {time.time() - self._startup_time:.2f} seconds")
  250. except Exception as e:
  251. self._status = ApplicationStatus.ERROR
  252. pprint(f"Failed to start application container: {e}")
  253. if self._debug_mode:
  254. pprint(traceback.format_exc())
  255. raise ApplicationContainerError(f"Application startup failed: {e}") from e
  256. def _initialize_mode_specific_components(self) -> None:
  257. """Initialize components specific to the current deployment mode."""
  258. if self._mode == DeploymentMode.SERVER:
  259. pprint("Initializing server mode components...")
  260. self.get_plugin_system()
  261. self.get_satellite_manager()
  262. network_manager = self.get_network_manager()
  263. self.get_scheduler()
  264. self.get_conversation_manager()
  265. self.get_arbitration_manager()
  266. # ML system is not typically needed on server
  267. # Start network server for SERVER mode
  268. self._start_network_server(network_manager)
  269. elif self._mode == DeploymentMode.CLIENT:
  270. pprint("Initializing client mode components...")
  271. network_manager = self.get_network_manager()
  272. ml_system = self.get_ml_system() # Client needs ML for wakeword/voice recognition
  273. # Client mode has minimal components
  274. # Connect to server for CLIENT mode
  275. self._connect_to_network_server(network_manager)
  276. # Start ML system for wakeword detection and voice recognition
  277. self._start_ml_system(ml_system)
  278. elif self._mode == DeploymentMode.STANDALONE:
  279. pprint("Initializing standalone mode components...")
  280. self.get_plugin_system()
  281. network_manager = self.get_network_manager()
  282. self.get_scheduler()
  283. self.get_conversation_manager()
  284. self.get_ml_system() # Standalone also needs ML processing
  285. # Standalone has most components but no satellite manager
  286. # Start network server for STANDALONE mode (can accept connections)
  287. self._start_network_server(network_manager)
  288. # Asset manager is available in all modes
  289. self.get_asset_manager()
  290. def _start_network_server(self, network_manager) -> None:
  291. """
  292. Start the network server with configuration from config manager.
  293. Args:
  294. network_manager: NetworkManager instance to start
  295. """
  296. try:
  297. pprint("Starting network server...")
  298. # Get configuration
  299. config_manager = self.get_config_manager()
  300. config = config_manager.get_config()
  301. # Extract network configuration
  302. network_config = getattr(config, 'network', None)
  303. if not network_config:
  304. raise ApplicationContainerError("No network configuration found")
  305. # Determine ports from configuration
  306. command_port = getattr(network_config, 'command_port', 2101)
  307. audio_ports = {
  308. 'input': getattr(network_config, 'raw_audio_input_port', 2102),
  309. 'output': getattr(network_config, 'raw_audio_output_port', 2103),
  310. 'music': getattr(network_config, 'raw_music_output_port', 2104)
  311. }
  312. # Start the network server
  313. network_manager.start_server(
  314. command_port=command_port,
  315. audio_ports=audio_ports
  316. )
  317. pprint(f"Network server started successfully on command port {command_port}")
  318. pprint(f"Audio streams: Input={audio_ports['input']}, Output={audio_ports['output']}, Music={audio_ports['music']}")
  319. # Update component health
  320. self._update_component_health('network_manager', ComponentStatus.RUNNING)
  321. except Exception as e:
  322. pprint(f"Failed to start network server: {e}")
  323. if self._debug_mode:
  324. pprint(traceback.format_exc())
  325. self._update_component_health('network_manager', ComponentStatus.ERROR, errors=[str(e)])
  326. raise ApplicationContainerError(f"Network server startup failed: {e}") from e
  327. def _start_ml_system(self, ml_system) -> None:
  328. """
  329. Start the ML system for wakeword detection and voice recognition.
  330. Args:
  331. ml_system: MLManager instance to start
  332. """
  333. try:
  334. pprint("Starting ML system...")
  335. # Start ML system asynchronously
  336. import asyncio
  337. loop = asyncio.new_event_loop()
  338. asyncio.set_event_loop(loop)
  339. loop.run_until_complete(ml_system.start())
  340. pprint("ML system started successfully")
  341. self._update_component_health('ml_system', ComponentStatus.RUNNING)
  342. except Exception as e:
  343. pprint(f"Failed to start ML system: {e}")
  344. if self._debug_mode:
  345. pprint(traceback.format_exc())
  346. self._update_component_health('ml_system', ComponentStatus.ERROR, errors=[str(e)])
  347. raise ApplicationContainerError(f"ML system startup failed: {e}") from e
  348. def _connect_to_network_server(self, network_manager) -> None:
  349. """
  350. Connect to a network server with configuration from config manager.
  351. Args:
  352. network_manager: NetworkManager instance to use for connection
  353. """
  354. try:
  355. pprint("Connecting to network server...")
  356. # Get configuration
  357. config_manager = self.get_config_manager()
  358. config = config_manager.get_config()
  359. # Extract satellite configuration for client mode
  360. satellite_config = getattr(config, 'satellite', None)
  361. if not satellite_config:
  362. raise ApplicationContainerError("No satellite configuration found for client mode")
  363. # Get server connection details
  364. server_host = getattr(satellite_config, 'server_host', 'localhost')
  365. server_port = getattr(satellite_config, 'server_port', 2101)
  366. # Attempt to connect to server
  367. success = network_manager.connect_to_server(server_host, server_port)
  368. if success:
  369. pprint(f"Connected to server at {server_host}:{server_port}")
  370. self._update_component_health('network_manager', ComponentStatus.RUNNING)
  371. else:
  372. pprint(f"Failed to connect to server at {server_host}:{server_port}")
  373. self._update_component_health('network_manager', ComponentStatus.ERROR, errors=["Connection failed"])
  374. # Check if auto-reconnect is enabled
  375. auto_reconnect = getattr(satellite_config, 'auto_reconnect', True)
  376. if auto_reconnect:
  377. pprint("Auto-reconnect is enabled - connection will be retried automatically")
  378. else:
  379. raise ApplicationContainerError(f"Failed to connect to server at {server_host}:{server_port}")
  380. except Exception as e:
  381. pprint(f"Failed to connect to network server: {e}")
  382. if self._debug_mode:
  383. pprint(traceback.format_exc())
  384. self._update_component_health('network_manager', ComponentStatus.ERROR, errors=[str(e)])
  385. raise ApplicationContainerError(f"Network server connection failed: {e}") from e
  386. def shutdown(self) -> None:
  387. """
  388. Gracefully shutdown the application container.
  389. This performs:
  390. 1. Component shutdown in reverse dependency order
  391. 2. Resource cleanup
  392. 3. Event system shutdown
  393. 4. Final status update
  394. """
  395. if self._status in [ApplicationStatus.SHUTTING_DOWN, ApplicationStatus.STOPPED]:
  396. pprint("Application container already shutting down or stopped")
  397. return
  398. pprint("Shutting down application container...")
  399. self._status = ApplicationStatus.SHUTTING_DOWN
  400. self._shutdown_time = time.time()
  401. try:
  402. # Trigger shutdown event if event handler is available
  403. if self._event_handler:
  404. shutdown_data = EventDataFactory.create_event_data(
  405. "system_shutdown",
  406. reason="normal_shutdown",
  407. graceful_shutdown=True,
  408. uptime_seconds=self._shutdown_time - (self._startup_time or 0)
  409. )
  410. self._event_handler.trigger_event("system_shutdown", shutdown_data)
  411. # Shutdown components in reverse dependency order
  412. shutdown_order = self._get_shutdown_order()
  413. for component_name in shutdown_order:
  414. self._shutdown_component(component_name)
  415. self._status = ApplicationStatus.STOPPED
  416. pprint("Application container shutdown completed")
  417. except Exception as e:
  418. self._status = ApplicationStatus.ERROR
  419. pprint(f"Error during shutdown: {e}")
  420. if self._debug_mode:
  421. pprint(traceback.format_exc())
  422. finally:
  423. # Clean up from singleton registry
  424. with self._lock:
  425. if hasattr(self, '_instance_key') and self._instance_key in self._instances:
  426. del self._instances[self._instance_key]
  427. def _get_shutdown_order(self) -> List[str]:
  428. """Get the order in which components should be shut down (reverse of dependency order)."""
  429. # For now, use a simple reverse order
  430. # This could be enhanced with proper topological sorting
  431. order = [
  432. 'arbitration_manager',
  433. 'conversation_manager',
  434. 'scheduler',
  435. 'ml_system', # Shutdown ML system before core components
  436. 'asset_manager',
  437. 'network_manager',
  438. 'satellite_manager',
  439. 'plugin_system',
  440. 'event_handler',
  441. 'config_manager'
  442. ]
  443. return [name for name in order if name in self._components]
  444. def _shutdown_component(self, component_name: str) -> None:
  445. """Shutdown a specific component safely."""
  446. if component_name not in self._components:
  447. return
  448. component = self._components[component_name]
  449. try:
  450. pprint(f"Shutting down {component_name}...")
  451. # Call shutdown method if available
  452. if hasattr(component, 'shutdown'):
  453. component.shutdown()
  454. elif hasattr(component, 'stop'):
  455. component.stop()
  456. elif hasattr(component, 'close'):
  457. component.close()
  458. # Update health status
  459. if component_name in self._component_health:
  460. self._component_health[component_name].status = ComponentStatus.STOPPED
  461. pprint(f"{component_name} shutdown completed")
  462. except Exception as e:
  463. pprint(f"Error shutting down {component_name}: {e}")
  464. if self._debug_mode:
  465. pprint(traceback.format_exc())
  466. # Component Factory Methods
  467. def _create_config_manager(self) -> ConfigManager:
  468. """Create and initialize the configuration manager."""
  469. pprint("Creating configuration manager...")
  470. config_manager = ConfigManager(
  471. mode=self._mode,
  472. config_file=self._config_path,
  473. debug_mode=self._debug_mode
  474. )
  475. # Load configuration
  476. config = config_manager.load_config()
  477. pprint(f"Configuration loaded successfully for {self._mode.value} mode")
  478. self._update_component_health('config_manager', ComponentStatus.INITIALIZED)
  479. return config_manager
  480. def _create_event_handler(self) -> EventHandler:
  481. """Create and initialize the event handler."""
  482. pprint("Creating event handler...")
  483. # Get configuration for event handler settings
  484. config_manager = self.get_config_manager()
  485. config = config_manager.get_config()
  486. # Create event handler with configuration
  487. event_handler = EventHandler(
  488. max_history_size=getattr(config.logging, 'max_event_history', 1000)
  489. )
  490. # Enable debug mode if needed
  491. if self._debug_mode:
  492. event_handler.enable_debug_mode(True)
  493. pprint("Event handler created successfully")
  494. self._update_component_health('event_handler', ComponentStatus.INITIALIZED)
  495. return event_handler
  496. def _create_plugin_system(self):
  497. """Create and initialize the plugin system."""
  498. pprint("Creating plugin system...")
  499. try:
  500. # Import the real plugin manager
  501. from .plugins import PluginManager
  502. # Get configuration for plugin system
  503. config_manager = self.get_config_manager()
  504. config = config_manager.get_config()
  505. # Create plugin manager with configuration
  506. plugin_manager = PluginManager(
  507. application=self,
  508. plugins_directory=getattr(config, 'plugins_directory', None),
  509. auto_load=getattr(config, 'auto_load_plugins', True),
  510. enable_hot_reload=self._debug_mode, # Enable hot-reload in debug mode
  511. health_check_interval=getattr(config, 'plugin_health_check_interval', 60.0),
  512. max_error_count=getattr(config, 'plugin_max_error_count', 5)
  513. )
  514. pprint("Plugin manager created successfully")
  515. self._update_component_health('plugin_system', ComponentStatus.INITIALIZED)
  516. return plugin_manager
  517. except ImportError as e:
  518. pprint(f"Failed to import PluginManager: {e}")
  519. # Fall back to placeholder
  520. plugin_system = PluginSystemPlaceholder(self)
  521. self._update_component_health('plugin_system', ComponentStatus.INITIALIZED)
  522. return plugin_system
  523. except Exception as e:
  524. pprint(f"Failed to create plugin manager: {e}")
  525. # Fall back to placeholder on any error
  526. plugin_system = PluginSystemPlaceholder(self)
  527. self._update_component_health('plugin_system', ComponentStatus.ERROR, errors=[str(e)])
  528. return plugin_system
  529. def _create_satellite_manager(self):
  530. """Create and initialize the satellite manager."""
  531. pprint("Creating satellite manager...")
  532. # Satellite manager will be implemented in future
  533. # For now, return a placeholder
  534. satellite_manager = SatelliteManagerPlaceholder(self)
  535. self._update_component_health('satellite_manager', ComponentStatus.INITIALIZED)
  536. return satellite_manager
  537. def _create_network_manager(self):
  538. """Create and initialize the network manager."""
  539. pprint("Creating network manager...")
  540. # Import the actual network manager
  541. try:
  542. from .network import NetworkManager
  543. network_manager = NetworkManager(self)
  544. pprint("Network manager created successfully")
  545. except ImportError as e:
  546. pprint(f"Failed to import NetworkManager: {e}")
  547. # Fall back to placeholder for now
  548. network_manager = NetworkManagerPlaceholder(self)
  549. self._update_component_health('network_manager', ComponentStatus.INITIALIZED)
  550. return network_manager
  551. def _create_asset_manager(self):
  552. """Create and initialize the asset manager."""
  553. pprint("Creating asset manager...")
  554. try:
  555. # Import the real asset manager
  556. from .assets import AssetManager, AssetManagerConfig, ProfileMode
  557. # Get configuration for asset manager
  558. config_manager = self.get_config_manager()
  559. config = config_manager.get_config()
  560. # Determine assets directory and profile from configuration
  561. assets_directory = getattr(config, 'assets_directory', './assets')
  562. asset_profile = getattr(config, 'asset_profile', None)
  563. # Create asset manager configuration
  564. asset_config = AssetManagerConfig(
  565. assets_directory=assets_directory,
  566. current_profile=asset_profile,
  567. cache_enabled=getattr(config, 'asset_cache_enabled', True),
  568. cache_max_size=getattr(config, 'asset_cache_max_size', 100),
  569. cache_max_memory=getattr(config, 'asset_cache_max_memory', 100 * 1024 * 1024),
  570. validation_enabled=getattr(config, 'asset_validation_enabled', True),
  571. hot_reload_enabled=self._debug_mode, # Enable hot-reload in debug mode
  572. hot_reload_debounce=getattr(config, 'asset_hot_reload_debounce', 0.5),
  573. preload_patterns=getattr(config, 'asset_preload_patterns', [
  574. "audio/success.wav",
  575. "audio/error.wav",
  576. "audio/notification.wav",
  577. "audio/wakeword_detected.wav"
  578. ]),
  579. profile_mode=ProfileMode.FALLBACK,
  580. thread_pool_size=getattr(config, 'asset_thread_pool_size', 4),
  581. enable_integrity_checks=getattr(config, 'asset_integrity_checks', True),
  582. auto_create_directories=True,
  583. compress_cached_assets=getattr(config, 'asset_cache_compression', False),
  584. asset_timeout=getattr(config, 'asset_timeout', 30.0)
  585. )
  586. # Create asset manager with configuration
  587. asset_manager = AssetManager(
  588. application_container=self,
  589. config=asset_config
  590. )
  591. pprint("Asset manager created successfully")
  592. self._update_component_health('asset_manager', ComponentStatus.INITIALIZED)
  593. return asset_manager
  594. except ImportError as e:
  595. pprint(f"Failed to import AssetManager: {e}")
  596. # Fall back to placeholder
  597. asset_manager = AssetManagerPlaceholder(self)
  598. self._update_component_health('asset_manager', ComponentStatus.INITIALIZED)
  599. return asset_manager
  600. except Exception as e:
  601. pprint(f"Failed to create asset manager: {e}")
  602. # Fall back to placeholder on any error
  603. asset_manager = AssetManagerPlaceholder(self)
  604. self._update_component_health('asset_manager', ComponentStatus.ERROR, errors=[str(e)])
  605. return asset_manager
  606. def _create_scheduler(self):
  607. """Create and initialize the scheduler."""
  608. pprint("Creating scheduler...")
  609. # Scheduler will be implemented in future
  610. # For now, return a placeholder
  611. scheduler = SchedulerPlaceholder(self)
  612. self._update_component_health('scheduler', ComponentStatus.INITIALIZED)
  613. return scheduler
  614. def _create_conversation_manager(self):
  615. """Create and initialize the conversation manager."""
  616. pprint("Creating conversation manager...")
  617. try:
  618. # Import the real conversation manager
  619. from .conversation import ConversationManager
  620. # Get configuration for conversation manager
  621. config_manager = self.get_config_manager()
  622. config = config_manager.get_config()
  623. # Create conversation manager with configuration
  624. conversation_manager = ConversationManager(
  625. application=self,
  626. max_concurrent_conversations=getattr(config, 'max_concurrent_conversations', 100),
  627. default_conversation_timeout=getattr(config, 'default_conversation_timeout', 300.0),
  628. plugin_question_timeout=getattr(config, 'plugin_question_timeout', 30.0),
  629. cleanup_interval_seconds=getattr(config, 'conversation_cleanup_interval', 60.0),
  630. max_conversation_history=getattr(config, 'max_conversation_history', 1000),
  631. enable_context_persistence=getattr(config, 'enable_conversation_persistence', False),
  632. context_storage_path=getattr(config, 'conversation_storage_path', None),
  633. enable_analytics=getattr(config, 'enable_conversation_analytics', True)
  634. )
  635. pprint("Conversation manager created successfully")
  636. self._update_component_health('conversation_manager', ComponentStatus.INITIALIZED)
  637. return conversation_manager
  638. except ImportError as e:
  639. pprint(f"Failed to import ConversationManager: {e}")
  640. # Fall back to placeholder
  641. conversation_manager = ConversationManagerPlaceholder(self)
  642. self._update_component_health('conversation_manager', ComponentStatus.INITIALIZED)
  643. return conversation_manager
  644. except Exception as e:
  645. pprint(f"Failed to create conversation manager: {e}")
  646. # Fall back to placeholder on any error
  647. conversation_manager = ConversationManagerPlaceholder(self)
  648. self._update_component_health('conversation_manager', ComponentStatus.ERROR, errors=[str(e)])
  649. return conversation_manager
  650. def _create_arbitration_manager(self):
  651. """Create and initialize the arbitration manager."""
  652. pprint("Creating arbitration manager...")
  653. # Arbitration manager will be implemented in future
  654. # For now, return a placeholder
  655. arbitration_manager = ArbitrationManagerPlaceholder(self)
  656. self._update_component_health('arbitration_manager', ComponentStatus.INITIALIZED)
  657. return arbitration_manager
  658. def _create_ml_system(self):
  659. """Create and initialize the ML system."""
  660. pprint("Creating ML system...")
  661. try:
  662. # Import the ML system
  663. from .ml import create_client_ml_system, create_standalone_ml_system, MLMode
  664. # Get configuration for ML system
  665. config_manager = self.get_config_manager()
  666. config = config_manager.get_config()
  667. # Get event handler for ML system integration
  668. event_handler = self.get_event_handler()
  669. # Determine which ML system to create based on deployment mode
  670. if self._mode == DeploymentMode.CLIENT:
  671. # Create client ML system
  672. ml_system = create_client_ml_system(
  673. application_container=self,
  674. enable_wakeword=getattr(config, 'enable_wakeword', True),
  675. enable_voice_recognition=getattr(config, 'enable_voice_recognition', True),
  676. enable_performance_monitoring=getattr(config, 'enable_ml_monitoring', True)
  677. )
  678. elif self._mode == DeploymentMode.STANDALONE:
  679. # Create standalone ML system
  680. ml_system = create_standalone_ml_system(
  681. application_container=self,
  682. enable_wakeword=getattr(config, 'enable_wakeword', True),
  683. enable_voice_recognition=getattr(config, 'enable_voice_recognition', True),
  684. enable_continuous_recognition=getattr(config, 'enable_continuous_recognition', True),
  685. enable_performance_monitoring=getattr(config, 'enable_ml_monitoring', True)
  686. )
  687. else:
  688. # For server mode, ML system might not be needed
  689. # Return a placeholder for now
  690. pprint("ML system not needed for server mode, using placeholder")
  691. ml_system = MLSystemPlaceholder(self)
  692. pprint("ML system created successfully")
  693. self._update_component_health('ml_system', ComponentStatus.INITIALIZED)
  694. return ml_system
  695. except ImportError as e:
  696. pprint(f"Failed to import ML system: {e}")
  697. # Fall back to placeholder
  698. ml_system = MLSystemPlaceholder(self)
  699. self._update_component_health('ml_system', ComponentStatus.INITIALIZED)
  700. return ml_system
  701. except Exception as e:
  702. pprint(f"Failed to create ML system: {e}")
  703. # Fall back to placeholder on any error
  704. ml_system = MLSystemPlaceholder(self)
  705. self._update_component_health('ml_system', ComponentStatus.ERROR, errors=[str(e)])
  706. return ml_system
  707. # Component Access Methods (Public API as specified in CLAUDE.md)
  708. def get_config_manager(self) -> ConfigManager:
  709. """Get the configuration manager instance."""
  710. return self._get_component('config_manager')
  711. def get_event_handler(self) -> EventHandler:
  712. """Get the event handler instance."""
  713. return self._get_component('event_handler')
  714. def get_plugin_system(self):
  715. """Get the plugin system instance."""
  716. return self._get_component('plugin_system')
  717. def get_satellite_manager(self):
  718. """Get the satellite manager instance."""
  719. return self._get_component('satellite_manager')
  720. def get_network_manager(self):
  721. """Get the network manager instance."""
  722. return self._get_component('network_manager')
  723. def get_asset_manager(self):
  724. """Get the asset manager instance."""
  725. return self._get_component('asset_manager')
  726. def get_scheduler(self):
  727. """Get the scheduler instance."""
  728. return self._get_component('scheduler')
  729. def get_conversation_manager(self):
  730. """Get the conversation manager instance."""
  731. return self._get_component('conversation_manager')
  732. def get_arbitration_manager(self):
  733. """Get the arbitration manager instance."""
  734. return self._get_component('arbitration_manager')
  735. def get_ml_system(self):
  736. """Get the ML system instance."""
  737. return self._get_component('ml_system')
  738. def _get_component(self, component_name: str):
  739. """
  740. Get a component instance, creating it if necessary (lazy loading).
  741. Args:
  742. component_name: Name of the component
  743. Returns:
  744. Component instance
  745. Raises:
  746. ComponentInitializationError: If component creation fails
  747. """
  748. with self._component_lock:
  749. # Return existing component if available
  750. if component_name in self._components:
  751. return self._components[component_name]
  752. # Check if factory exists
  753. if component_name not in self._component_factories:
  754. raise ComponentInitializationError(f"No factory registered for component: {component_name}")
  755. # Initialize dependencies first
  756. self._ensure_dependencies(component_name)
  757. # Create the component
  758. try:
  759. self._update_component_health(component_name, ComponentStatus.INITIALIZING)
  760. factory = self._component_factories[component_name]
  761. component = factory()
  762. self._components[component_name] = component
  763. self._update_component_health(component_name, ComponentStatus.RUNNING)
  764. pprint(f"Component '{component_name}' initialized successfully")
  765. return component
  766. except Exception as e:
  767. self._update_component_health(component_name, ComponentStatus.ERROR, errors=[str(e)])
  768. raise ComponentInitializationError(f"Failed to initialize {component_name}: {e}") from e
  769. def _ensure_dependencies(self, component_name: str) -> None:
  770. """Ensure all dependencies for a component are initialized."""
  771. if component_name not in self._component_dependencies:
  772. return
  773. dependencies = self._component_dependencies[component_name]
  774. for dep_name in dependencies:
  775. if dep_name not in self._components:
  776. pprint(f"Initializing dependency '{dep_name}' for '{component_name}'")
  777. self._get_component(dep_name)
  778. def _update_component_health(
  779. self,
  780. component_name: str,
  781. status: ComponentStatus,
  782. errors: Optional[List[str]] = None,
  783. warnings: Optional[List[str]] = None,
  784. info: Optional[Dict[str, Any]] = None
  785. ) -> None:
  786. """Update the health status of a component."""
  787. self._component_health[component_name] = ComponentHealth(
  788. name=component_name,
  789. status=status,
  790. last_check=time.time(),
  791. errors=errors or [],
  792. warnings=warnings or [],
  793. info=info or {}
  794. )
  795. # Health and Status Methods
  796. def get_status(self) -> ApplicationStatus:
  797. """Get the current application status."""
  798. return self._status
  799. def get_health_report(self) -> Dict[str, Any]:
  800. """
  801. Get a comprehensive health report for the application and all components.
  802. Returns:
  803. Dictionary containing health information
  804. """
  805. return {
  806. 'application': {
  807. 'status': self._status.value,
  808. 'mode': self._mode.value,
  809. 'uptime': time.time() - (self._startup_time or time.time()),
  810. 'startup_time': self._startup_time,
  811. 'debug_mode': self._debug_mode,
  812. },
  813. 'components': {
  814. name: {
  815. 'status': health.status.value,
  816. 'is_healthy': health.is_healthy(),
  817. 'last_check': health.last_check,
  818. 'errors': health.errors,
  819. 'warnings': health.warnings,
  820. 'info': health.info
  821. }
  822. for name, health in self._component_health.items()
  823. },
  824. 'summary': {
  825. 'total_components': len(self._component_health),
  826. 'healthy_components': sum(1 for h in self._component_health.values() if h.is_healthy()),
  827. 'error_components': sum(1 for h in self._component_health.values() if h.status == ComponentStatus.ERROR),
  828. }
  829. }
  830. def is_healthy(self) -> bool:
  831. """Check if the application and all components are healthy."""
  832. if self._status != ApplicationStatus.RUNNING:
  833. return False
  834. return all(health.is_healthy() for health in self._component_health.values())
  835. # Utility Methods
  836. def get_version(self) -> str:
  837. """Get the application version."""
  838. return "1.0.0"
  839. def get_mode(self) -> DeploymentMode:
  840. """Get the deployment mode."""
  841. return self._mode
  842. def is_debug_mode(self) -> bool:
  843. """Check if debug mode is enabled."""
  844. return self._debug_mode
  845. def get_uptime(self) -> float:
  846. """Get application uptime in seconds."""
  847. if self._startup_time is None:
  848. return 0.0
  849. return time.time() - self._startup_time
  850. @classmethod
  851. def get_instance(cls, mode: Union[str, DeploymentMode] = None) -> Optional['ApplicationContainer']:
  852. """
  853. Get an existing application container instance.
  854. Args:
  855. mode: Deployment mode to find (optional)
  856. Returns:
  857. ApplicationContainer instance or None if not found
  858. """
  859. with cls._lock:
  860. if mode is None:
  861. # Return any available instance
  862. return next(iter(cls._instances.values()), None)
  863. if isinstance(mode, str):
  864. mode = DeploymentMode(mode.lower())
  865. # Find instance with matching mode
  866. for instance in cls._instances.values():
  867. if instance._mode == mode:
  868. return instance
  869. return None
  870. @classmethod
  871. def cleanup_instances(cls) -> None:
  872. """Clean up all application container instances."""
  873. with cls._lock:
  874. for instance in list(cls._instances.values()):
  875. try:
  876. instance.shutdown()
  877. except Exception as e:
  878. pprint(f"Error shutting down instance: {e}")
  879. cls._instances.clear()
  880. # Placeholder classes for components that will be implemented later
  881. # These provide the basic interface expected by the application container
  882. class ComponentPlaceholder:
  883. """Base placeholder for components that haven't been implemented yet."""
  884. def __init__(self, application: ApplicationContainer):
  885. self.application = application
  886. self.application_ref = weakref.ref(application)
  887. def shutdown(self):
  888. """Shutdown method for proper cleanup."""
  889. pass
  890. class PluginSystemPlaceholder(ComponentPlaceholder):
  891. """Placeholder for the plugin system."""
  892. pass
  893. class SatelliteManagerPlaceholder(ComponentPlaceholder):
  894. """Placeholder for the satellite manager."""
  895. pass
  896. class NetworkManagerPlaceholder(ComponentPlaceholder):
  897. """Placeholder for the network manager."""
  898. pass
  899. class AssetManagerPlaceholder(ComponentPlaceholder):
  900. """Placeholder for the asset manager."""
  901. pass
  902. class SchedulerPlaceholder(ComponentPlaceholder):
  903. """Placeholder for the scheduler."""
  904. pass
  905. class ConversationManagerPlaceholder(ComponentPlaceholder):
  906. """Placeholder for the conversation manager."""
  907. pass
  908. class ArbitrationManagerPlaceholder(ComponentPlaceholder):
  909. """Placeholder for the arbitration manager."""
  910. pass
  911. class MLSystemPlaceholder(ComponentPlaceholder):
  912. """Placeholder for the ML system."""
  913. pass
  914. # Utility Functions
  915. def create_application_container(
  916. mode: Union[str, DeploymentMode],
  917. config_path: Optional[str] = None,
  918. debug_mode: bool = False,
  919. **kwargs
  920. ) -> ApplicationContainer:
  921. """
  922. Convenience function to create an ApplicationContainer.
  923. Args:
  924. mode: Deployment mode
  925. config_path: Path to configuration file
  926. debug_mode: Enable debug mode
  927. **kwargs: Additional arguments
  928. Returns:
  929. ApplicationContainer instance
  930. """
  931. return ApplicationContainer(
  932. mode=mode,
  933. config_path=config_path,
  934. debug_mode=debug_mode,
  935. **kwargs
  936. )
  937. def get_application_container(mode: Union[str, DeploymentMode] = None) -> Optional[ApplicationContainer]:
  938. """
  939. Get an existing application container instance.
  940. Args:
  941. mode: Deployment mode (optional)
  942. Returns:
  943. ApplicationContainer instance or None
  944. """
  945. return ApplicationContainer.get_instance(mode)
  946. def cleanup_application_containers() -> None:
  947. """Clean up all application container instances."""
  948. ApplicationContainer.cleanup_instances()
  949. # Module exports
  950. __all__ = [
  951. 'ApplicationContainer',
  952. 'ApplicationContainerError',
  953. 'ComponentInitializationError',
  954. 'ComponentLifecycleError',
  955. 'ApplicationStatus',
  956. 'ComponentStatus',
  957. 'ComponentHealth',
  958. 'create_application_container',
  959. 'get_application_container',
  960. 'cleanup_application_containers',
  961. 'pprint'
  962. ]