Michael Hu commited on
Commit
8a0c4b0
·
1 Parent(s): 111538d

Create application services

Browse files
src/application/services/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ """Application services package."""
2
+
3
+ from .audio_processing_service import AudioProcessingApplicationService
4
+ from .configuration_service import ConfigurationApplicationService
5
+
6
+ __all__ = [
7
+ 'AudioProcessingApplicationService',
8
+ 'ConfigurationApplicationService'
9
+ ]
src/application/services/audio_processing_service.py ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Audio Processing Application Service for pipeline orchestration."""
2
+
3
+ import logging
4
+ import os
5
+ import tempfile
6
+ import time
7
+ import uuid
8
+ from pathlib import Path
9
+ from typing import Optional, Dict, Any
10
+ from contextlib import contextmanager
11
+
12
+ from ..dtos.audio_upload_dto import AudioUploadDto
13
+ from ..dtos.processing_request_dto import ProcessingRequestDto
14
+ from ..dtos.processing_result_dto import ProcessingResultDto
15
+ from ...domain.interfaces.speech_recognition import ISpeechRecognitionService
16
+ from ...domain.interfaces.translation import ITranslationService
17
+ from ...domain.interfaces.speech_synthesis import ISpeechSynthesisService
18
+ from ...domain.models.audio_content import AudioContent
19
+ from ...domain.models.text_content import TextContent
20
+ from ...domain.models.translation_request import TranslationRequest
21
+ from ...domain.models.speech_synthesis_request import SpeechSynthesisRequest
22
+ from ...domain.models.voice_settings import VoiceSettings
23
+ from ...domain.exceptions import (
24
+ DomainException,
25
+ AudioProcessingException,
26
+ SpeechRecognitionException,
27
+ TranslationFailedException,
28
+ SpeechSynthesisException
29
+ )
30
+ from ...infrastructure.config.app_config import AppConfig
31
+ from ...infrastructure.config.dependency_container import DependencyContainer
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class AudioProcessingApplicationService:
37
+ """Application service for orchestrating the complete audio processing pipeline."""
38
+
39
+ def __init__(
40
+ self,
41
+ container: DependencyContainer,
42
+ config: Optional[AppConfig] = None
43
+ ):
44
+ """
45
+ Initialize the audio processing application service.
46
+
47
+ Args:
48
+ container: Dependency injection container
49
+ config: Application configuration (optional, will be resolved from container)
50
+ """
51
+ self._container = container
52
+ self._config = config or container.resolve(AppConfig)
53
+ self._temp_files: Dict[str, str] = {} # Track temporary files for cleanup
54
+
55
+ # Setup logging
56
+ self._setup_logging()
57
+
58
+ logger.info("AudioProcessingApplicationService initialized")
59
+
60
+ def _setup_logging(self) -> None:
61
+ """Setup logging configuration."""
62
+ try:
63
+ log_config = self._config.get_logging_config()
64
+
65
+ # Configure logger level
66
+ logger.setLevel(getattr(logging, log_config['level'].upper(), logging.INFO))
67
+
68
+ # Add file handler if enabled
69
+ if log_config.get('enable_file_logging', False):
70
+ file_handler = logging.FileHandler(log_config['log_file_path'])
71
+ file_handler.setLevel(logger.level)
72
+
73
+ formatter = logging.Formatter(log_config['format'])
74
+ file_handler.setFormatter(formatter)
75
+
76
+ logger.addHandler(file_handler)
77
+
78
+ except Exception as e:
79
+ logger.warning(f"Failed to setup logging configuration: {e}")
80
+
81
+ def process_audio_pipeline(self, request: ProcessingRequestDto) -> ProcessingResultDto:
82
+ """
83
+ Process audio through the complete pipeline: STT -> Translation -> TTS.
84
+
85
+ Args:
86
+ request: Processing request containing audio and parameters
87
+
88
+ Returns:
89
+ ProcessingResultDto: Result of the complete processing pipeline
90
+ """
91
+ correlation_id = str(uuid.uuid4())
92
+ start_time = time.time()
93
+
94
+ logger.info(f"Starting audio processing pipeline [correlation_id={correlation_id}]")
95
+
96
+ try:
97
+ # Validate request
98
+ self._validate_request(request)
99
+
100
+ # Create temporary working directory
101
+ with self._create_temp_directory(correlation_id) as temp_dir:
102
+ # Step 1: Convert uploaded audio to domain model
103
+ audio_content = self._convert_upload_to_audio_content(request.audio, temp_dir)
104
+
105
+ # Step 2: Speech-to-Text
106
+ original_text = self._perform_speech_recognition(
107
+ audio_content,
108
+ request.asr_model,
109
+ correlation_id
110
+ )
111
+
112
+ # Step 3: Translation (if needed)
113
+ translated_text = self._perform_translation(
114
+ original_text,
115
+ request.source_language,
116
+ request.target_language,
117
+ correlation_id
118
+ ) if request.requires_translation else original_text
119
+
120
+ # Step 4: Text-to-Speech
121
+ output_audio_path = self._perform_speech_synthesis(
122
+ translated_text,
123
+ request.voice,
124
+ request.speed,
125
+ request.target_language,
126
+ temp_dir,
127
+ correlation_id
128
+ )
129
+
130
+ # Calculate processing time
131
+ processing_time = time.time() - start_time
132
+
133
+ # Create successful result
134
+ result = ProcessingResultDto.success_result(
135
+ original_text=original_text.text,
136
+ translated_text=translated_text.text if translated_text != original_text else None,
137
+ audio_path=output_audio_path,
138
+ processing_time=processing_time,
139
+ metadata={
140
+ 'correlation_id': correlation_id,
141
+ 'asr_model': request.asr_model,
142
+ 'target_language': request.target_language,
143
+ 'voice': request.voice,
144
+ 'speed': request.speed,
145
+ 'translation_required': request.requires_translation
146
+ }
147
+ )
148
+
149
+ logger.info(
150
+ f"Audio processing pipeline completed successfully "
151
+ f"[correlation_id={correlation_id}, processing_time={processing_time:.2f}s]"
152
+ )
153
+
154
+ return result
155
+
156
+ except DomainException as e:
157
+ processing_time = time.time() - start_time
158
+ error_code = self._get_error_code_from_exception(e)
159
+
160
+ logger.error(
161
+ f"Domain error in audio processing pipeline: {e} "
162
+ f"[correlation_id={correlation_id}, processing_time={processing_time:.2f}s]"
163
+ )
164
+
165
+ return ProcessingResultDto.error_result(
166
+ error_message=str(e),
167
+ error_code=error_code,
168
+ processing_time=processing_time,
169
+ metadata={'correlation_id': correlation_id}
170
+ )
171
+
172
+ except Exception as e:
173
+ processing_time = time.time() - start_time
174
+
175
+ logger.error(
176
+ f"Unexpected error in audio processing pipeline: {e} "
177
+ f"[correlation_id={correlation_id}, processing_time={processing_time:.2f}s]",
178
+ exc_info=True
179
+ )
180
+
181
+ return ProcessingResultDto.error_result(
182
+ error_message=f"System error: {str(e)}",
183
+ error_code='SYSTEM_ERROR',
184
+ processing_time=processing_time,
185
+ metadata={'correlation_id': correlation_id}
186
+ )
187
+
188
+ finally:
189
+ # Cleanup temporary files
190
+ self._cleanup_temp_files()
191
+
192
+ def _validate_request(self, request: ProcessingRequestDto) -> None:
193
+ """
194
+ Validate processing request.
195
+
196
+ Args:
197
+ request: Processing request to validate
198
+
199
+ Raises:
200
+ ValueError: If request is invalid
201
+ """
202
+ if not isinstance(request, ProcessingRequestDto):
203
+ raise ValueError("Request must be a ProcessingRequestDto instance")
204
+
205
+ # Additional validation beyond DTO validation
206
+ processing_config = self._config.get_processing_config()
207
+
208
+ # Check file size limits
209
+ max_size_bytes = processing_config['max_file_size_mb'] * 1024 * 1024
210
+ if request.audio.size > max_size_bytes:
211
+ raise ValueError(
212
+ f"Audio file too large: {request.audio.size} bytes. "
213
+ f"Maximum allowed: {max_size_bytes} bytes"
214
+ )
215
+
216
+ # Check supported audio formats
217
+ supported_formats = processing_config['supported_audio_formats']
218
+ file_ext = request.audio.file_extension.lstrip('.')
219
+ if file_ext not in supported_formats:
220
+ raise ValueError(
221
+ f"Unsupported audio format: {file_ext}. "
222
+ f"Supported formats: {supported_formats}"
223
+ )
224
+
225
+ @contextmanager
226
+ def _create_temp_directory(self, correlation_id: str):
227
+ """
228
+ Create temporary directory for processing.
229
+
230
+ Args:
231
+ correlation_id: Correlation ID for tracking
232
+
233
+ Yields:
234
+ str: Path to temporary directory
235
+ """
236
+ processing_config = self._config.get_processing_config()
237
+ base_temp_dir = processing_config['temp_dir']
238
+
239
+ # Create unique temp directory
240
+ temp_dir = os.path.join(base_temp_dir, f"processing_{correlation_id}")
241
+
242
+ try:
243
+ os.makedirs(temp_dir, exist_ok=True)
244
+ logger.debug(f"Created temporary directory: {temp_dir}")
245
+ yield temp_dir
246
+
247
+ finally:
248
+ # Cleanup temp directory if configured
249
+ if processing_config.get('cleanup_temp_files', True):
250
+ try:
251
+ import shutil
252
+ shutil.rmtree(temp_dir, ignore_errors=True)
253
+ logger.debug(f"Cleaned up temporary directory: {temp_dir}")
254
+ except Exception as e:
255
+ logger.warning(f"Failed to cleanup temp directory {temp_dir}: {e}")
256
+
257
+ def _convert_upload_to_audio_content(
258
+ self,
259
+ upload: AudioUploadDto,
260
+ temp_dir: str
261
+ ) -> AudioContent:
262
+ """
263
+ Convert uploaded audio to domain AudioContent.
264
+
265
+ Args:
266
+ upload: Audio upload DTO
267
+ temp_dir: Temporary directory for file operations
268
+
269
+ Returns:
270
+ AudioContent: Domain audio content model
271
+
272
+ Raises:
273
+ AudioProcessingException: If conversion fails
274
+ """
275
+ try:
276
+ # Save uploaded content to temporary file
277
+ temp_file_path = os.path.join(temp_dir, f"input_{upload.filename}")
278
+
279
+ with open(temp_file_path, 'wb') as f:
280
+ f.write(upload.content)
281
+
282
+ # Track temp file for cleanup
283
+ self._temp_files[temp_file_path] = temp_file_path
284
+
285
+ # Determine audio format from file extension
286
+ audio_format = upload.file_extension.lstrip('.').lower()
287
+
288
+ # Create AudioContent (simplified - in real implementation would extract metadata)
289
+ audio_content = AudioContent(
290
+ data=upload.content,
291
+ format=audio_format,
292
+ sample_rate=16000, # Default, would be extracted from actual file
293
+ duration=0.0 # Would be calculated from actual file
294
+ )
295
+
296
+ logger.debug(f"Converted upload to AudioContent: {upload.filename}")
297
+ return audio_content
298
+
299
+ except Exception as e:
300
+ logger.error(f"Failed to convert upload to AudioContent: {e}")
301
+ raise AudioProcessingException(f"Failed to process uploaded audio: {str(e)}")
302
+
303
+ def _perform_speech_recognition(
304
+ self,
305
+ audio: AudioContent,
306
+ model: str,
307
+ correlation_id: str
308
+ ) -> TextContent:
309
+ """
310
+ Perform speech-to-text recognition.
311
+
312
+ Args:
313
+ audio: Audio content to transcribe
314
+ model: STT model to use
315
+ correlation_id: Correlation ID for tracking
316
+
317
+ Returns:
318
+ TextContent: Transcribed text
319
+
320
+ Raises:
321
+ SpeechRecognitionException: If STT fails
322
+ """
323
+ try:
324
+ logger.debug(f"Starting STT with model: {model} [correlation_id={correlation_id}]")
325
+
326
+ # Get STT provider from container
327
+ stt_provider = self._container.get_stt_provider(model)
328
+
329
+ # Perform transcription
330
+ text_content = stt_provider.transcribe(audio, model)
331
+
332
+ logger.info(
333
+ f"STT completed successfully [correlation_id={correlation_id}, "
334
+ f"text_length={len(text_content.text)}]"
335
+ )
336
+
337
+ return text_content
338
+
339
+ except Exception as e:
340
+ logger.error(f"STT failed: {e} [correlation_id={correlation_id}]")
341
+ raise SpeechRecognitionException(f"Speech recognition failed: {str(e)}")
342
+
343
+ def _perform_translation(
344
+ self,
345
+ text: TextContent,
346
+ source_language: Optional[str],
347
+ target_language: str,
348
+ correlation_id: str
349
+ ) -> TextContent:
350
+ """
351
+ Perform text translation.
352
+
353
+ Args:
354
+ text: Text to translate
355
+ source_language: Source language (optional, auto-detect if None)
356
+ target_language: Target language
357
+ correlation_id: Correlation ID for tracking
358
+
359
+ Returns:
360
+ TextContent: Translated text
361
+
362
+ Raises:
363
+ TranslationFailedException: If translation fails
364
+ """
365
+ try:
366
+ logger.debug(
367
+ f"Starting translation: {source_language or 'auto'} -> {target_language} "
368
+ f"[correlation_id={correlation_id}]"
369
+ )
370
+
371
+ # Get translation provider from container
372
+ translation_provider = self._container.get_translation_provider()
373
+
374
+ # Create translation request
375
+ translation_request = TranslationRequest(
376
+ text=text.text,
377
+ source_language=source_language or 'auto',
378
+ target_language=target_language
379
+ )
380
+
381
+ # Perform translation
382
+ translated_text = translation_provider.translate(translation_request)
383
+
384
+ logger.info(
385
+ f"Translation completed successfully [correlation_id={correlation_id}, "
386
+ f"source_length={len(text.text)}, target_length={len(translated_text.text)}]"
387
+ )
388
+
389
+ return translated_text
390
+
391
+ except Exception as e:
392
+ logger.error(f"Translation failed: {e} [correlation_id={correlation_id}]")
393
+ raise TranslationFailedException(f"Translation failed: {str(e)}")
394
+
395
+ def _perform_speech_synthesis(
396
+ self,
397
+ text: TextContent,
398
+ voice: str,
399
+ speed: float,
400
+ language: str,
401
+ temp_dir: str,
402
+ correlation_id: str
403
+ ) -> str:
404
+ """
405
+ Perform text-to-speech synthesis.
406
+
407
+ Args:
408
+ text: Text to synthesize
409
+ voice: Voice to use
410
+ speed: Speech speed
411
+ language: Target language
412
+ temp_dir: Temporary directory for output
413
+ correlation_id: Correlation ID for tracking
414
+
415
+ Returns:
416
+ str: Path to generated audio file
417
+
418
+ Raises:
419
+ SpeechSynthesisException: If TTS fails
420
+ """
421
+ try:
422
+ logger.debug(
423
+ f"Starting TTS with voice: {voice}, speed: {speed} "
424
+ f"[correlation_id={correlation_id}]"
425
+ )
426
+
427
+ # Get TTS provider from container
428
+ tts_provider = self._container.get_tts_provider(voice)
429
+
430
+ # Create voice settings
431
+ voice_settings = VoiceSettings(
432
+ voice_id=voice,
433
+ speed=speed,
434
+ language=language
435
+ )
436
+
437
+ # Create synthesis request
438
+ synthesis_request = SpeechSynthesisRequest(
439
+ text=text.text,
440
+ voice_settings=voice_settings
441
+ )
442
+
443
+ # Perform synthesis
444
+ audio_content = tts_provider.synthesize(synthesis_request)
445
+
446
+ # Save output to file
447
+ output_filename = f"output_{correlation_id}.{audio_content.format}"
448
+ output_path = os.path.join(temp_dir, output_filename)
449
+
450
+ with open(output_path, 'wb') as f:
451
+ f.write(audio_content.data)
452
+
453
+ # Track temp file for cleanup
454
+ self._temp_files[output_path] = output_path
455
+
456
+ logger.info(
457
+ f"TTS completed successfully [correlation_id={correlation_id}, "
458
+ f"output_file={output_path}]"
459
+ )
460
+
461
+ return output_path
462
+
463
+ except Exception as e:
464
+ logger.error(f"TTS failed: {e} [correlation_id={correlation_id}]")
465
+ raise SpeechSynthesisException(f"Speech synthesis failed: {str(e)}")
466
+
467
+ def _get_error_code_from_exception(self, exception: Exception) -> str:
468
+ """
469
+ Get error code from exception type.
470
+
471
+ Args:
472
+ exception: Exception instance
473
+
474
+ Returns:
475
+ str: Error code
476
+ """
477
+ if isinstance(exception, SpeechRecognitionException):
478
+ return 'STT_ERROR'
479
+ elif isinstance(exception, TranslationFailedException):
480
+ return 'TRANSLATION_ERROR'
481
+ elif isinstance(exception, SpeechSynthesisException):
482
+ return 'TTS_ERROR'
483
+ elif isinstance(exception, ValueError):
484
+ return 'VALIDATION_ERROR'
485
+ else:
486
+ return 'SYSTEM_ERROR'
487
+
488
+ def _cleanup_temp_files(self) -> None:
489
+ """Cleanup tracked temporary files."""
490
+ for file_path in list(self._temp_files.keys()):
491
+ try:
492
+ if os.path.exists(file_path):
493
+ os.remove(file_path)
494
+ logger.debug(f"Cleaned up temp file: {file_path}")
495
+ except Exception as e:
496
+ logger.warning(f"Failed to cleanup temp file {file_path}: {e}")
497
+ finally:
498
+ # Remove from tracking regardless of success
499
+ self._temp_files.pop(file_path, None)
500
+
501
+ def get_processing_status(self, correlation_id: str) -> Dict[str, Any]:
502
+ """
503
+ Get processing status for a correlation ID.
504
+
505
+ Args:
506
+ correlation_id: Correlation ID to check
507
+
508
+ Returns:
509
+ Dict[str, Any]: Processing status information
510
+ """
511
+ # This would be implemented with actual status tracking
512
+ # For now, return basic info
513
+ return {
514
+ 'correlation_id': correlation_id,
515
+ 'status': 'unknown',
516
+ 'message': 'Status tracking not implemented'
517
+ }
518
+
519
+ def get_supported_configurations(self) -> Dict[str, Any]:
520
+ """
521
+ Get supported configurations for the processing pipeline.
522
+
523
+ Returns:
524
+ Dict[str, Any]: Supported configurations
525
+ """
526
+ return {
527
+ 'asr_models': ['whisper-small', 'whisper-medium', 'whisper-large', 'parakeet'],
528
+ 'voices': ['kokoro', 'dia', 'cosyvoice2', 'dummy'],
529
+ 'languages': [
530
+ 'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh',
531
+ 'ar', 'hi', 'tr', 'pl', 'nl', 'sv', 'da', 'no', 'fi'
532
+ ],
533
+ 'audio_formats': self._config.get_processing_config()['supported_audio_formats'],
534
+ 'max_file_size_mb': self._config.get_processing_config()['max_file_size_mb'],
535
+ 'speed_range': {'min': 0.5, 'max': 2.0}
536
+ }
537
+
538
+ def cleanup(self) -> None:
539
+ """Cleanup application service resources."""
540
+ logger.info("Cleaning up AudioProcessingApplicationService")
541
+
542
+ # Cleanup temporary files
543
+ self._cleanup_temp_files()
544
+
545
+ logger.info("AudioProcessingApplicationService cleanup completed")
546
+
547
+ def __enter__(self):
548
+ """Context manager entry."""
549
+ return self
550
+
551
+ def __exit__(self, exc_type, exc_val, exc_tb):
552
+ """Context manager exit with cleanup."""
553
+ self.cleanup()
src/application/services/configuration_service.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration Application Service for settings management."""
2
+
3
+ import logging
4
+ import os
5
+ import json
6
+ from typing import Dict, List, Any, Optional, Union
7
+ from pathlib import Path
8
+ from dataclasses import asdict
9
+
10
+ from ...infrastructure.config.app_config import AppConfig, TTSConfig, STTConfig, TranslationConfig, ProcessingConfig, LoggingConfig
11
+ from ...infrastructure.config.dependency_container import DependencyContainer
12
+ from ...domain.exceptions import DomainException
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ConfigurationException(DomainException):
18
+ """Exception raised for configuration-related errors."""
19
+ pass
20
+
21
+
22
+ class ConfigurationApplicationService:
23
+ """Application service for managing application configuration and settings."""
24
+
25
+ def __init__(
26
+ self,
27
+ container: DependencyContainer,
28
+ config: Optional[AppConfig] = None
29
+ ):
30
+ """
31
+ Initialize the configuration application service.
32
+
33
+ Args:
34
+ container: Dependency injection container
35
+ config: Application configuration (optional, will be resolved from container)
36
+ """
37
+ self._container = container
38
+ self._config = config or container.resolve(AppConfig)
39
+
40
+ logger.info("ConfigurationApplicationService initialized")
41
+
42
+ def get_current_configuration(self) -> Dict[str, Any]:
43
+ """
44
+ Get the current application configuration.
45
+
46
+ Returns:
47
+ Dict[str, Any]: Current configuration as dictionary
48
+ """
49
+ try:
50
+ return {
51
+ 'tts': self._config.get_tts_config(),
52
+ 'stt': self._config.get_stt_config(),
53
+ 'translation': self._config.get_translation_config(),
54
+ 'processing': self._config.get_processing_config(),
55
+ 'logging': self._config.get_logging_config()
56
+ }
57
+ except Exception as e:
58
+ logger.error(f"Failed to get current configuration: {e}")
59
+ raise ConfigurationException(f"Failed to retrieve configuration: {str(e)}")
60
+
61
+ def get_tts_configuration(self) -> Dict[str, Any]:
62
+ """
63
+ Get TTS-specific configuration.
64
+
65
+ Returns:
66
+ Dict[str, Any]: TTS configuration
67
+ """
68
+ try:
69
+ return self._config.get_tts_config()
70
+ except Exception as e:
71
+ logger.error(f"Failed to get TTS configuration: {e}")
72
+ raise ConfigurationException(f"Failed to retrieve TTS configuration: {str(e)}")
73
+
74
+ def get_stt_configuration(self) -> Dict[str, Any]:
75
+ """
76
+ Get STT-specific configuration.
77
+
78
+ Returns:
79
+ Dict[str, Any]: STT configuration
80
+ """
81
+ try:
82
+ return self._config.get_stt_config()
83
+ except Exception as e:
84
+ logger.error(f"Failed to get STT configuration: {e}")
85
+ raise ConfigurationException(f"Failed to retrieve STT configuration: {str(e)}")
86
+
87
+ def get_translation_configuration(self) -> Dict[str, Any]:
88
+ """
89
+ Get translation-specific configuration.
90
+
91
+ Returns:
92
+ Dict[str, Any]: Translation configuration
93
+ """
94
+ try:
95
+ return self._config.get_translation_config()
96
+ except Exception as e:
97
+ logger.error(f"Failed to get translation configuration: {e}")
98
+ raise ConfigurationException(f"Failed to retrieve translation configuration: {str(e)}")
99
+
100
+ def get_processing_configuration(self) -> Dict[str, Any]:
101
+ """
102
+ Get processing-specific configuration.
103
+
104
+ Returns:
105
+ Dict[str, Any]: Processing configuration
106
+ """
107
+ try:
108
+ return self._config.get_processing_config()
109
+ except Exception as e:
110
+ logger.error(f"Failed to get processing configuration: {e}")
111
+ raise ConfigurationException(f"Failed to retrieve processing configuration: {str(e)}")
112
+
113
+ def get_logging_configuration(self) -> Dict[str, Any]:
114
+ """
115
+ Get logging-specific configuration.
116
+
117
+ Returns:
118
+ Dict[str, Any]: Logging configuration
119
+ """
120
+ try:
121
+ return self._config.get_logging_config()
122
+ except Exception as e:
123
+ logger.error(f"Failed to get logging configuration: {e}")
124
+ raise ConfigurationException(f"Failed to retrieve logging configuration: {str(e)}")
125
+
126
+ def update_tts_configuration(self, updates: Dict[str, Any]) -> Dict[str, Any]:
127
+ """
128
+ Update TTS configuration.
129
+
130
+ Args:
131
+ updates: Configuration updates to apply
132
+
133
+ Returns:
134
+ Dict[str, Any]: Updated TTS configuration
135
+
136
+ Raises:
137
+ ConfigurationException: If update fails or validation fails
138
+ """
139
+ try:
140
+ # Validate updates
141
+ self._validate_tts_updates(updates)
142
+
143
+ # Apply updates to current config
144
+ current_config = self._config.get_tts_config()
145
+
146
+ for key, value in updates.items():
147
+ if key in current_config:
148
+ # Update the actual config object
149
+ if hasattr(self._config.tts, key):
150
+ setattr(self._config.tts, key, value)
151
+ logger.debug(f"Updated TTS config: {key} = {value}")
152
+ else:
153
+ logger.warning(f"Unknown TTS configuration key: {key}")
154
+
155
+ # Return updated configuration
156
+ updated_config = self._config.get_tts_config()
157
+ logger.info(f"TTS configuration updated: {list(updates.keys())}")
158
+
159
+ return updated_config
160
+
161
+ except Exception as e:
162
+ logger.error(f"Failed to update TTS configuration: {e}")
163
+ raise ConfigurationException(f"Failed to update TTS configuration: {str(e)}")
164
+
165
+ def update_stt_configuration(self, updates: Dict[str, Any]) -> Dict[str, Any]:
166
+ """
167
+ Update STT configuration.
168
+
169
+ Args:
170
+ updates: Configuration updates to apply
171
+
172
+ Returns:
173
+ Dict[str, Any]: Updated STT configuration
174
+
175
+ Raises:
176
+ ConfigurationException: If update fails or validation fails
177
+ """
178
+ try:
179
+ # Validate updates
180
+ self._validate_stt_updates(updates)
181
+
182
+ # Apply updates to current config
183
+ current_config = self._config.get_stt_config()
184
+
185
+ for key, value in updates.items():
186
+ if key in current_config:
187
+ # Update the actual config object
188
+ if hasattr(self._config.stt, key):
189
+ setattr(self._config.stt, key, value)
190
+ logger.debug(f"Updated STT config: {key} = {value}")
191
+ else:
192
+ logger.warning(f"Unknown STT configuration key: {key}")
193
+
194
+ # Return updated configuration
195
+ updated_config = self._config.get_stt_config()
196
+ logger.info(f"STT configuration updated: {list(updates.keys())}")
197
+
198
+ return updated_config
199
+
200
+ except Exception as e:
201
+ logger.error(f"Failed to update STT configuration: {e}")
202
+ raise ConfigurationException(f"Failed to update STT configuration: {str(e)}")
203
+
204
+ def update_translation_configuration(self, updates: Dict[str, Any]) -> Dict[str, Any]:
205
+ """
206
+ Update translation configuration.
207
+
208
+ Args:
209
+ updates: Configuration updates to apply
210
+
211
+ Returns:
212
+ Dict[str, Any]: Updated translation configuration
213
+
214
+ Raises:
215
+ ConfigurationException: If update fails or validation fails
216
+ """
217
+ try:
218
+ # Validate updates
219
+ self._validate_translation_updates(updates)
220
+
221
+ # Apply updates to current config
222
+ current_config = self._config.get_translation_config()
223
+
224
+ for key, value in updates.items():
225
+ if key in current_config:
226
+ # Update the actual config object
227
+ if hasattr(self._config.translation, key):
228
+ setattr(self._config.translation, key, value)
229
+ logger.debug(f"Updated translation config: {key} = {value}")
230
+ else:
231
+ logger.warning(f"Unknown translation configuration key: {key}")
232
+
233
+ # Return updated configuration
234
+ updated_config = self._config.get_translation_config()
235
+ logger.info(f"Translation configuration updated: {list(updates.keys())}")
236
+
237
+ return updated_config
238
+
239
+ except Exception as e:
240
+ logger.error(f"Failed to update translation configuration: {e}")
241
+ raise ConfigurationException(f"Failed to update translation configuration: {str(e)}")
242
+
243
+ def update_processing_configuration(self, updates: Dict[str, Any]) -> Dict[str, Any]:
244
+ """
245
+ Update processing configuration.
246
+
247
+ Args:
248
+ updates: Configuration updates to apply
249
+
250
+ Returns:
251
+ Dict[str, Any]: Updated processing configuration
252
+
253
+ Raises:
254
+ ConfigurationException: If update fails or validation fails
255
+ """
256
+ try:
257
+ # Validate updates
258
+ self._validate_processing_updates(updates)
259
+
260
+ # Apply updates to current config
261
+ current_config = self._config.get_processing_config()
262
+
263
+ for key, value in updates.items():
264
+ if key in current_config:
265
+ # Update the actual config object
266
+ if hasattr(self._config.processing, key):
267
+ setattr(self._config.processing, key, value)
268
+ logger.debug(f"Updated processing config: {key} = {value}")
269
+ else:
270
+ logger.warning(f"Unknown processing configuration key: {key}")
271
+
272
+ # Return updated configuration
273
+ updated_config = self._config.get_processing_config()
274
+ logger.info(f"Processing configuration updated: {list(updates.keys())}")
275
+
276
+ return updated_config
277
+
278
+ except Exception as e:
279
+ logger.error(f"Failed to update processing configuration: {e}")
280
+ raise ConfigurationException(f"Failed to update processing configuration: {str(e)}")
281
+
282
+ def _validate_tts_updates(self, updates: Dict[str, Any]) -> None:
283
+ """
284
+ Validate TTS configuration updates.
285
+
286
+ Args:
287
+ updates: Updates to validate
288
+
289
+ Raises:
290
+ ConfigurationException: If validation fails
291
+ """
292
+ valid_providers = ['kokoro', 'dia', 'cosyvoice2', 'dummy']
293
+ valid_languages = ['en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh']
294
+
295
+ for key, value in updates.items():
296
+ if key == 'preferred_providers':
297
+ if not isinstance(value, list):
298
+ raise ConfigurationException("preferred_providers must be a list")
299
+ for provider in value:
300
+ if provider not in valid_providers:
301
+ raise ConfigurationException(f"Invalid TTS provider: {provider}")
302
+
303
+ elif key == 'default_speed':
304
+ if not isinstance(value, (int, float)) or not (0.1 <= value <= 3.0):
305
+ raise ConfigurationException("default_speed must be between 0.1 and 3.0")
306
+
307
+ elif key == 'default_language':
308
+ if value not in valid_languages:
309
+ raise ConfigurationException(f"Invalid language: {value}")
310
+
311
+ elif key == 'enable_streaming':
312
+ if not isinstance(value, bool):
313
+ raise ConfigurationException("enable_streaming must be a boolean")
314
+
315
+ elif key == 'max_text_length':
316
+ if not isinstance(value, int) or value <= 0:
317
+ raise ConfigurationException("max_text_length must be a positive integer")
318
+
319
+ def _validate_stt_updates(self, updates: Dict[str, Any]) -> None:
320
+ """
321
+ Validate STT configuration updates.
322
+
323
+ Args:
324
+ updates: Updates to validate
325
+
326
+ Raises:
327
+ ConfigurationException: If validation fails
328
+ """
329
+ valid_providers = ['whisper', 'parakeet']
330
+
331
+ for key, value in updates.items():
332
+ if key == 'preferred_providers':
333
+ if not isinstance(value, list):
334
+ raise ConfigurationException("preferred_providers must be a list")
335
+ for provider in value:
336
+ if provider not in valid_providers:
337
+ raise ConfigurationException(f"Invalid STT provider: {provider}")
338
+
339
+ elif key == 'default_model':
340
+ if value not in valid_providers:
341
+ raise ConfigurationException(f"Invalid STT model: {value}")
342
+
343
+ elif key == 'chunk_length_s':
344
+ if not isinstance(value, int) or value <= 0:
345
+ raise ConfigurationException("chunk_length_s must be a positive integer")
346
+
347
+ elif key == 'batch_size':
348
+ if not isinstance(value, int) or value <= 0:
349
+ raise ConfigurationException("batch_size must be a positive integer")
350
+
351
+ elif key == 'enable_vad':
352
+ if not isinstance(value, bool):
353
+ raise ConfigurationException("enable_vad must be a boolean")
354
+
355
+ def _validate_translation_updates(self, updates: Dict[str, Any]) -> None:
356
+ """
357
+ Validate translation configuration updates.
358
+
359
+ Args:
360
+ updates: Updates to validate
361
+
362
+ Raises:
363
+ ConfigurationException: If validation fails
364
+ """
365
+ for key, value in updates.items():
366
+ if key == 'default_provider':
367
+ if not isinstance(value, str) or not value:
368
+ raise ConfigurationException("default_provider must be a non-empty string")
369
+
370
+ elif key == 'model_name':
371
+ if not isinstance(value, str) or not value:
372
+ raise ConfigurationException("model_name must be a non-empty string")
373
+
374
+ elif key == 'max_chunk_length':
375
+ if not isinstance(value, int) or value <= 0:
376
+ raise ConfigurationException("max_chunk_length must be a positive integer")
377
+
378
+ elif key == 'batch_size':
379
+ if not isinstance(value, int) or value <= 0:
380
+ raise ConfigurationException("batch_size must be a positive integer")
381
+
382
+ elif key == 'cache_translations':
383
+ if not isinstance(value, bool):
384
+ raise ConfigurationException("cache_translations must be a boolean")
385
+
386
+ def _validate_processing_updates(self, updates: Dict[str, Any]) -> None:
387
+ """
388
+ Validate processing configuration updates.
389
+
390
+ Args:
391
+ updates: Updates to validate
392
+
393
+ Raises:
394
+ ConfigurationException: If validation fails
395
+ """
396
+ for key, value in updates.items():
397
+ if key == 'temp_dir':
398
+ if not isinstance(value, str) or not value:
399
+ raise ConfigurationException("temp_dir must be a non-empty string")
400
+ # Try to create directory to validate path
401
+ try:
402
+ Path(value).mkdir(parents=True, exist_ok=True)
403
+ except Exception as e:
404
+ raise ConfigurationException(f"Invalid temp_dir path: {e}")
405
+
406
+ elif key == 'cleanup_temp_files':
407
+ if not isinstance(value, bool):
408
+ raise ConfigurationException("cleanup_temp_files must be a boolean")
409
+
410
+ elif key == 'max_file_size_mb':
411
+ if not isinstance(value, int) or value <= 0:
412
+ raise ConfigurationException("max_file_size_mb must be a positive integer")
413
+
414
+ elif key == 'supported_audio_formats':
415
+ if not isinstance(value, list):
416
+ raise ConfigurationException("supported_audio_formats must be a list")
417
+ valid_formats = ['wav', 'mp3', 'flac', 'ogg', 'm4a']
418
+ for fmt in value:
419
+ if fmt not in valid_formats:
420
+ raise ConfigurationException(f"Invalid audio format: {fmt}")
421
+
422
+ elif key == 'processing_timeout_seconds':
423
+ if not isinstance(value, int) or value <= 0:
424
+ raise ConfigurationException("processing_timeout_seconds must be a positive integer")
425
+
426
+ def save_configuration_to_file(self, file_path: str) -> None:
427
+ """
428
+ Save current configuration to file.
429
+
430
+ Args:
431
+ file_path: Path to save configuration file
432
+
433
+ Raises:
434
+ ConfigurationException: If save fails
435
+ """
436
+ try:
437
+ self._config.save_configuration(file_path)
438
+ logger.info(f"Configuration saved to {file_path}")
439
+ except Exception as e:
440
+ logger.error(f"Failed to save configuration to {file_path}: {e}")
441
+ raise ConfigurationException(f"Failed to save configuration: {str(e)}")
442
+
443
+ def load_configuration_from_file(self, file_path: str) -> Dict[str, Any]:
444
+ """
445
+ Load configuration from file.
446
+
447
+ Args:
448
+ file_path: Path to configuration file
449
+
450
+ Returns:
451
+ Dict[str, Any]: Loaded configuration
452
+
453
+ Raises:
454
+ ConfigurationException: If load fails
455
+ """
456
+ try:
457
+ if not os.path.exists(file_path):
458
+ raise ConfigurationException(f"Configuration file not found: {file_path}")
459
+
460
+ # Create new config instance with the file
461
+ new_config = AppConfig(config_file=file_path)
462
+
463
+ # Update current config
464
+ self._config = new_config
465
+
466
+ # Update container with new config
467
+ self._container.register_singleton(AppConfig, new_config)
468
+
469
+ logger.info(f"Configuration loaded from {file_path}")
470
+
471
+ return self.get_current_configuration()
472
+
473
+ except Exception as e:
474
+ logger.error(f"Failed to load configuration from {file_path}: {e}")
475
+ raise ConfigurationException(f"Failed to load configuration: {str(e)}")
476
+
477
+ def reload_configuration(self) -> Dict[str, Any]:
478
+ """
479
+ Reload configuration from original sources.
480
+
481
+ Returns:
482
+ Dict[str, Any]: Reloaded configuration
483
+
484
+ Raises:
485
+ ConfigurationException: If reload fails
486
+ """
487
+ try:
488
+ self._config.reload_configuration()
489
+ logger.info("Configuration reloaded successfully")
490
+
491
+ return self.get_current_configuration()
492
+
493
+ except Exception as e:
494
+ logger.error(f"Failed to reload configuration: {e}")
495
+ raise ConfigurationException(f"Failed to reload configuration: {str(e)}")
496
+
497
+ def get_provider_availability(self) -> Dict[str, Dict[str, bool]]:
498
+ """
499
+ Get availability status of all providers.
500
+
501
+ Returns:
502
+ Dict[str, Dict[str, bool]]: Provider availability by category
503
+ """
504
+ try:
505
+ availability = {
506
+ 'tts': {},
507
+ 'stt': {},
508
+ 'translation': {}
509
+ }
510
+
511
+ # Check TTS providers
512
+ tts_factory = self._container.resolve(type(self._container._get_tts_factory()))
513
+ for provider in ['kokoro', 'dia', 'cosyvoice2', 'dummy']:
514
+ try:
515
+ tts_factory.create_provider(provider)
516
+ availability['tts'][provider] = True
517
+ except Exception:
518
+ availability['tts'][provider] = False
519
+
520
+ # Check STT providers
521
+ stt_factory = self._container.resolve(type(self._container._get_stt_factory()))
522
+ for provider in ['whisper', 'parakeet']:
523
+ try:
524
+ stt_factory.create_provider(provider)
525
+ availability['stt'][provider] = True
526
+ except Exception:
527
+ availability['stt'][provider] = False
528
+
529
+ # Check translation providers
530
+ translation_factory = self._container.resolve(type(self._container._get_translation_factory()))
531
+ try:
532
+ translation_factory.get_default_provider()
533
+ availability['translation']['nllb'] = True
534
+ except Exception:
535
+ availability['translation']['nllb'] = False
536
+
537
+ return availability
538
+
539
+ except Exception as e:
540
+ logger.error(f"Failed to check provider availability: {e}")
541
+ raise ConfigurationException(f"Failed to check provider availability: {str(e)}")
542
+
543
+ def get_system_info(self) -> Dict[str, Any]:
544
+ """
545
+ Get system information and configuration summary.
546
+
547
+ Returns:
548
+ Dict[str, Any]: System information
549
+ """
550
+ try:
551
+ return {
552
+ 'config_file': self._config.config_file,
553
+ 'temp_directory': self._config.processing.temp_dir,
554
+ 'log_level': self._config.logging.level,
555
+ 'provider_availability': self.get_provider_availability(),
556
+ 'supported_languages': [
557
+ 'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh',
558
+ 'ar', 'hi', 'tr', 'pl', 'nl', 'sv', 'da', 'no', 'fi'
559
+ ],
560
+ 'supported_audio_formats': self._config.processing.supported_audio_formats,
561
+ 'max_file_size_mb': self._config.processing.max_file_size_mb,
562
+ 'processing_timeout_seconds': self._config.processing.processing_timeout_seconds
563
+ }
564
+ except Exception as e:
565
+ logger.error(f"Failed to get system info: {e}")
566
+ raise ConfigurationException(f"Failed to get system info: {str(e)}")
567
+
568
+ def validate_configuration(self) -> Dict[str, List[str]]:
569
+ """
570
+ Validate current configuration and return any issues.
571
+
572
+ Returns:
573
+ Dict[str, List[str]]: Validation issues by category
574
+ """
575
+ issues = {
576
+ 'tts': [],
577
+ 'stt': [],
578
+ 'translation': [],
579
+ 'processing': [],
580
+ 'logging': []
581
+ }
582
+
583
+ try:
584
+ # Validate TTS configuration
585
+ tts_config = self._config.get_tts_config()
586
+ if not (0.1 <= tts_config['default_speed'] <= 3.0):
587
+ issues['tts'].append(f"Invalid default_speed: {tts_config['default_speed']}")
588
+
589
+ if tts_config['max_text_length'] <= 0:
590
+ issues['tts'].append(f"Invalid max_text_length: {tts_config['max_text_length']}")
591
+
592
+ # Validate STT configuration
593
+ stt_config = self._config.get_stt_config()
594
+ if stt_config['chunk_length_s'] <= 0:
595
+ issues['stt'].append(f"Invalid chunk_length_s: {stt_config['chunk_length_s']}")
596
+
597
+ if stt_config['batch_size'] <= 0:
598
+ issues['stt'].append(f"Invalid batch_size: {stt_config['batch_size']}")
599
+
600
+ # Validate processing configuration
601
+ processing_config = self._config.get_processing_config()
602
+ if not os.path.exists(processing_config['temp_dir']):
603
+ issues['processing'].append(f"Temp directory does not exist: {processing_config['temp_dir']}")
604
+
605
+ if processing_config['max_file_size_mb'] <= 0:
606
+ issues['processing'].append(f"Invalid max_file_size_mb: {processing_config['max_file_size_mb']}")
607
+
608
+ # Validate logging configuration
609
+ logging_config = self._config.get_logging_config()
610
+ valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
611
+ if logging_config['level'].upper() not in valid_levels:
612
+ issues['logging'].append(f"Invalid log level: {logging_config['level']}")
613
+
614
+ except Exception as e:
615
+ issues['general'] = [f"Configuration validation error: {str(e)}"]
616
+
617
+ return issues
618
+
619
+ def reset_to_defaults(self) -> Dict[str, Any]:
620
+ """
621
+ Reset configuration to default values.
622
+
623
+ Returns:
624
+ Dict[str, Any]: Reset configuration
625
+
626
+ Raises:
627
+ ConfigurationException: If reset fails
628
+ """
629
+ try:
630
+ # Create new config with defaults
631
+ default_config = AppConfig()
632
+
633
+ # Update current config
634
+ self._config = default_config
635
+
636
+ # Update container with new config
637
+ self._container.register_singleton(AppConfig, default_config)
638
+
639
+ logger.info("Configuration reset to defaults")
640
+
641
+ return self.get_current_configuration()
642
+
643
+ except Exception as e:
644
+ logger.error(f"Failed to reset configuration: {e}")
645
+ raise ConfigurationException(f"Failed to reset configuration: {str(e)}")
646
+
647
+ def cleanup(self) -> None:
648
+ """Cleanup configuration service resources."""
649
+ logger.info("Cleaning up ConfigurationApplicationService")
650
+ # No specific cleanup needed for configuration service
651
+ logger.info("ConfigurationApplicationService cleanup completed")
652
+
653
+ def __enter__(self):
654
+ """Context manager entry."""
655
+ return self
656
+
657
+ def __exit__(self, exc_type, exc_val, exc_tb):
658
+ """Context manager exit with cleanup."""
659
+ self.cleanup()