"""Container setup module for registering all services and dependencies.""" import logging from typing import Optional from .app_config import AppConfig from .dependency_container import DependencyContainer, ServiceLifetime from ..tts.provider_factory import TTSProviderFactory from ..stt.provider_factory import STTProviderFactory from ..translation.provider_factory import TranslationProviderFactory from ...domain.interfaces.audio_processing import IAudioProcessingService from ...domain.interfaces.speech_recognition import ISpeechRecognitionService from ...domain.interfaces.translation import ITranslationService from ...domain.interfaces.speech_synthesis import ISpeechSynthesisService from ...domain.services.audio_processing_service import AudioProcessingService from ...application.services.audio_processing_service import AudioProcessingApplicationService from ...application.services.configuration_service import ConfigurationApplicationService logger = logging.getLogger(__name__) def setup_container(config_file: Optional[str] = None) -> DependencyContainer: """ Set up and configure the dependency injection container with all services. Args: config_file: Optional path to configuration file Returns: DependencyContainer: Configured container instance """ logger.info("Setting up dependency injection container") # Create configuration config = AppConfig(config_file=config_file) # Create container with configuration container = DependencyContainer(config) # Register core services _register_core_services(container, config) # Register domain services _register_domain_services(container) # Register application services _register_application_services(container) # Register provider services _register_provider_services(container) logger.info("Dependency injection container setup completed") return container def _register_core_services(container: DependencyContainer, config: AppConfig) -> None: """ Register core infrastructure services. Args: container: Dependency container config: Application configuration """ logger.info("Registering core services") # Configuration is already registered as singleton in container constructor # But we ensure it's the same instance container.register_singleton(AppConfig, config) # Register provider factories as singletons container.register_singleton( TTSProviderFactory, lambda: TTSProviderFactory() ) container.register_singleton( STTProviderFactory, lambda: STTProviderFactory() ) container.register_singleton( TranslationProviderFactory, lambda: TranslationProviderFactory() ) logger.info("Core services registered") def _register_domain_services(container: DependencyContainer) -> None: """ Register domain services. Args: container: Dependency container """ logger.info("Registering domain services") # Register domain audio processing service as transient # It requires other services to be injected def create_audio_processing_service() -> IAudioProcessingService: """Factory function for creating audio processing service.""" # Get provider services from container stt_provider = container.get_stt_provider() translation_provider = container.get_translation_provider() tts_provider = container.get_tts_provider() return AudioProcessingService( speech_recognition_service=stt_provider, translation_service=translation_provider, speech_synthesis_service=tts_provider ) container.register_transient( IAudioProcessingService, create_audio_processing_service ) logger.info("Domain services registered") def _register_application_services(container: DependencyContainer) -> None: """ Register application services. Args: container: Dependency container """ logger.info("Registering application services") # Register audio processing application service as scoped # It manages resources and should be scoped to request/session def create_audio_processing_app_service() -> AudioProcessingApplicationService: """Factory function for creating audio processing application service.""" return AudioProcessingApplicationService(container) container.register_scoped( AudioProcessingApplicationService, create_audio_processing_app_service ) # Register configuration application service as singleton # Configuration service can be shared across requests def create_configuration_app_service() -> ConfigurationApplicationService: """Factory function for creating configuration application service.""" return ConfigurationApplicationService(container) container.register_singleton( ConfigurationApplicationService, create_configuration_app_service ) logger.info("Application services registered") def _register_provider_services(container: DependencyContainer) -> None: """ Register provider services with fallback logic. Args: container: Dependency container """ logger.info("Registering provider services") # Register TTS provider service as transient with fallback def create_tts_provider() -> ISpeechSynthesisService: """Factory function for creating TTS provider with fallback.""" return container.get_tts_provider() container.register_transient( ISpeechSynthesisService, create_tts_provider ) # Register STT provider service as transient with fallback def create_stt_provider() -> ISpeechRecognitionService: """Factory function for creating STT provider with fallback.""" return container.get_stt_provider() container.register_transient( ISpeechRecognitionService, create_stt_provider ) # Register translation provider service as transient with fallback def create_translation_provider() -> ITranslationService: """Factory function for creating translation provider with fallback.""" return container.get_translation_provider() container.register_transient( ITranslationService, create_translation_provider ) logger.info("Provider services registered") def configure_logging(config: AppConfig) -> None: """ Configure application logging based on configuration. Args: config: Application configuration """ try: logging_config = config.get_logging_config() # Configure root logger root_logger = logging.getLogger() root_logger.setLevel(getattr(logging, logging_config['level'].upper(), logging.INFO)) # Clear existing handlers for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) # Create console handler console_handler = logging.StreamHandler() console_handler.setLevel(root_logger.level) # Create formatter formatter = logging.Formatter(logging_config['format']) console_handler.setFormatter(formatter) # Add console handler root_logger.addHandler(console_handler) # Add file handler if enabled if logging_config.get('enable_file_logging', False): from logging.handlers import RotatingFileHandler file_handler = RotatingFileHandler( logging_config['log_file_path'], maxBytes=logging_config['max_log_file_size_mb'] * 1024 * 1024, backupCount=logging_config['backup_count'] ) file_handler.setLevel(root_logger.level) file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) logger.info(f"Logging configured: level={logging_config['level']}") except Exception as e: # Fallback to basic logging configuration logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger.warning(f"Failed to configure logging from config, using defaults: {e}") def create_configured_container(config_file: Optional[str] = None) -> DependencyContainer: """ Create and configure a complete dependency injection container. This is the main entry point for setting up the application's dependency injection. Args: config_file: Optional path to configuration file Returns: DependencyContainer: Fully configured container """ try: logger.info("Creating configured container...") # Setup container logger.info("Setting up container...") container = setup_container(config_file) logger.info("Container setup completed") # Configure logging logger.info("Configuring logging...") config = container.resolve(AppConfig) configure_logging(config) logger.info("Logging configuration completed") # Validate container setup logger.info("Starting container validation...") try: _validate_container_setup(container) logger.info("Container validation completed") except Exception as validation_error: logger.error(f"Container validation failed: {validation_error}", exception=validation_error) # For now, let's continue even if validation fails to see if the app works logger.warning("Continuing despite validation failure...") logger.info("Application container created and configured successfully") return container except Exception as e: logger.error(f"Failed to create configured container: {e}", exception=e) raise def _validate_container_setup(container: DependencyContainer) -> None: """ Validate that the container is properly set up. Args: container: Container to validate Raises: RuntimeError: If validation fails """ logger.info("Validating container setup") required_services = [ AppConfig, TTSProviderFactory, STTProviderFactory, TranslationProviderFactory, IAudioProcessingService, AudioProcessingApplicationService, ConfigurationApplicationService, ISpeechSynthesisService, ISpeechRecognitionService, ITranslationService ] missing_services = [] logger.info("Checking service registrations...") for service_type in required_services: logger.info(f"Checking registration for: {service_type.__name__}") if not container.is_registered(service_type): missing_services.append(service_type.__name__) logger.error(f"Service not registered: {service_type.__name__}") else: logger.info(f"Service registered: {service_type.__name__}") if missing_services: error_msg = f"Container validation failed. Missing services: {missing_services}" logger.error(error_msg) raise RuntimeError(error_msg) # Test service resolution logger.info("Testing service resolution...") try: logger.info("Resolving AppConfig...") config = container.resolve(AppConfig) logger.info("AppConfig resolved successfully") logger.info("Resolving AudioProcessingApplicationService...") app_service = container.resolve(AudioProcessingApplicationService) logger.info("AudioProcessingApplicationService resolved successfully") logger.info("Resolving ConfigurationApplicationService...") config_service = container.resolve(ConfigurationApplicationService) logger.info("ConfigurationApplicationService resolved successfully") logger.info("Container validation successful") except Exception as e: error_msg = f"Container validation failed during service resolution: {e}" logger.error(error_msg, exception=e) raise RuntimeError(error_msg) def get_service_registry_info(container: DependencyContainer) -> dict: """ Get information about registered services in the container. Args: container: Container to inspect Returns: dict: Service registry information """ try: registered_services = container.get_registered_services() # Get provider availability config = container.resolve(AppConfig) tts_factory = container.resolve(TTSProviderFactory) stt_factory = container.resolve(STTProviderFactory) translation_factory = container.resolve(TranslationProviderFactory) provider_info = { 'tts_providers': tts_factory.get_available_providers(), 'stt_providers': stt_factory.get_available_providers(), 'translation_providers': [p.value for p in translation_factory.get_available_providers()] } return { 'registered_services': registered_services, 'provider_availability': provider_info, 'configuration_summary': { 'config_file': config.config_file, 'temp_dir': config.processing.temp_dir, 'log_level': config.logging.level, 'tts_preferred_providers': config.tts.preferred_providers, 'stt_default_model': config.stt.default_model, 'translation_default_provider': config.translation.default_provider } } except Exception as e: logger.error(f"Failed to get service registry info: {e}") return { 'error': str(e), 'registered_services': container.get_registered_services() if container else {} } # Global container instance management _global_container: Optional[DependencyContainer] = None def initialize_global_container(config_file: Optional[str] = None) -> DependencyContainer: """ Initialize the global container instance. Args: config_file: Optional configuration file path Returns: DependencyContainer: Global container instance """ global _global_container if _global_container is not None: logger.warning("Global container already initialized, cleaning up previous instance") _global_container.cleanup() _global_container = create_configured_container(config_file) logger.info("Global container initialized") return _global_container def get_global_container() -> DependencyContainer: """ Get the global container instance. Returns: DependencyContainer: Global container instance Raises: RuntimeError: If container is not initialized """ global _global_container if _global_container is None: logger.info("Global container not initialized, creating with defaults") _global_container = create_configured_container() return _global_container def cleanup_global_container() -> None: """Cleanup the global container instance.""" global _global_container if _global_container is not None: _global_container.cleanup() _global_container = None logger.info("Global container cleaned up")