Spaces:
Runtime error
Runtime error
🥚 Initial DigiPal deployment to HuggingFace Spaces🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>
4399e64
| """ | |
| Error handling integration module for DigiPal. | |
| This module provides a unified interface for all error handling, | |
| recovery, and user messaging functionality across the DigiPal system. | |
| """ | |
| import logging | |
| import asyncio | |
| from typing import Dict, List, Optional, Any, Callable, Union | |
| from datetime import datetime | |
| from dataclasses import dataclass | |
| from .exceptions import DigiPalException, ErrorSeverity, ErrorCategory | |
| from .error_handler import error_handler, with_error_handling, with_retry, RetryConfig | |
| from .recovery_strategies import get_system_recovery_orchestrator, RecoveryResult | |
| from .user_error_messages import ( | |
| get_user_friendly_error_message, get_recovery_guide, | |
| MessageTone, user_message_generator | |
| ) | |
| from ..storage.backup_recovery import BackupRecoveryManager | |
| from ..ai.graceful_degradation import ai_service_manager | |
| logger = logging.getLogger(__name__) | |
| class ErrorHandlingResult: | |
| """Result of comprehensive error handling.""" | |
| success: bool | |
| original_error: DigiPalException | |
| user_message: str | |
| recovery_attempted: bool | |
| recovery_result: Optional[RecoveryResult] | |
| recovery_guide: List[Dict[str, Any]] | |
| fallback_value: Any | |
| context: Dict[str, Any] | |
| class DigiPalErrorHandler: | |
| """Unified error handler for the entire DigiPal system.""" | |
| def __init__(self, backup_manager: Optional[BackupRecoveryManager] = None): | |
| """ | |
| Initialize the unified error handler. | |
| Args: | |
| backup_manager: Backup manager for recovery operations | |
| """ | |
| self.backup_manager = backup_manager | |
| self.error_callbacks: Dict[str, List[Callable]] = {} | |
| self.user_context: Dict[str, Any] = {} | |
| self.message_tone = MessageTone.FRIENDLY | |
| # Initialize recovery system if backup manager is provided | |
| if backup_manager: | |
| from .recovery_strategies import initialize_system_recovery | |
| initialize_system_recovery(backup_manager) | |
| logger.info("DigiPal unified error handler initialized") | |
| def set_user_context(self, context: Dict[str, Any]): | |
| """ | |
| Set user context for personalized error messages. | |
| Args: | |
| context: User context information | |
| """ | |
| self.user_context.update(context) | |
| def set_message_tone(self, tone: MessageTone): | |
| """ | |
| Set the tone for error messages. | |
| Args: | |
| tone: Message tone to use | |
| """ | |
| self.message_tone = tone | |
| def register_error_callback(self, error_category: str, callback: Callable): | |
| """ | |
| Register a callback for specific error categories. | |
| Args: | |
| error_category: Error category to listen for | |
| callback: Callback function to execute | |
| """ | |
| if error_category not in self.error_callbacks: | |
| self.error_callbacks[error_category] = [] | |
| self.error_callbacks[error_category].append(callback) | |
| logger.info(f"Registered error callback for category: {error_category}") | |
| def handle_error_comprehensive( | |
| self, | |
| error: Exception, | |
| context: Optional[Dict[str, Any]] = None, | |
| attempt_recovery: bool = True, | |
| provide_fallback: bool = True, | |
| fallback_value: Any = None | |
| ) -> ErrorHandlingResult: | |
| """ | |
| Comprehensive error handling with recovery and user messaging. | |
| Args: | |
| error: The error that occurred | |
| context: Additional context information | |
| attempt_recovery: Whether to attempt automatic recovery | |
| provide_fallback: Whether to provide fallback functionality | |
| fallback_value: Fallback value to return | |
| Returns: | |
| ErrorHandlingResult with complete handling information | |
| """ | |
| start_time = datetime.now() | |
| context = context or {} | |
| try: | |
| # Convert to DigiPal exception if needed | |
| if not isinstance(error, DigiPalException): | |
| digipal_error = error_handler.handle_error(error, context) | |
| else: | |
| digipal_error = error | |
| digipal_error.context.update(context) | |
| # Log the error | |
| error_handler.log_error(digipal_error, {'handling_start': start_time.isoformat()}) | |
| # Generate user-friendly message | |
| user_message = get_user_friendly_error_message( | |
| digipal_error, | |
| tone=self.message_tone, | |
| user_context=self.user_context | |
| ) | |
| # Get recovery guide | |
| recovery_guide = get_recovery_guide(digipal_error, max_steps=4) | |
| # Attempt recovery if requested and appropriate | |
| recovery_result = None | |
| recovery_attempted = False | |
| if attempt_recovery and digipal_error.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]: | |
| recovery_attempted = True | |
| orchestrator = get_system_recovery_orchestrator() | |
| if orchestrator: | |
| recovery_result = orchestrator.execute_comprehensive_recovery(digipal_error) | |
| if recovery_result.success: | |
| logger.info(f"Recovery successful for error: {digipal_error.error_code}") | |
| user_message = user_message_generator.generate_success_message( | |
| digipal_error.category.value, | |
| recovery_result.strategy_used | |
| ) | |
| else: | |
| logger.warning(f"Recovery failed for error: {digipal_error.error_code}") | |
| # Handle graceful degradation for AI services | |
| if digipal_error.category == ErrorCategory.AI_MODEL: | |
| self._handle_ai_service_degradation(digipal_error) | |
| # Execute registered callbacks | |
| self._execute_error_callbacks(digipal_error) | |
| # Determine success based on recovery and severity | |
| overall_success = ( | |
| recovery_result.success if recovery_result | |
| else digipal_error.severity in [ErrorSeverity.LOW, ErrorSeverity.MEDIUM] | |
| ) | |
| # Prepare result | |
| result = ErrorHandlingResult( | |
| success=overall_success, | |
| original_error=digipal_error, | |
| user_message=user_message, | |
| recovery_attempted=recovery_attempted, | |
| recovery_result=recovery_result, | |
| recovery_guide=recovery_guide, | |
| fallback_value=fallback_value if provide_fallback else None, | |
| context={ | |
| **digipal_error.context, | |
| 'handling_duration_ms': (datetime.now() - start_time).total_seconds() * 1000, | |
| 'user_context': self.user_context, | |
| 'message_tone': self.message_tone.value | |
| } | |
| ) | |
| return result | |
| except Exception as handling_error: | |
| logger.error(f"Error handling failed: {handling_error}") | |
| # Return minimal error result | |
| return ErrorHandlingResult( | |
| success=False, | |
| original_error=error_handler.handle_error(handling_error), | |
| user_message="An unexpected error occurred during error handling. Please restart the application.", | |
| recovery_attempted=False, | |
| recovery_result=None, | |
| recovery_guide=[], | |
| fallback_value=fallback_value if provide_fallback else None, | |
| context={'handling_error': str(handling_error)} | |
| ) | |
| def _handle_ai_service_degradation(self, error: DigiPalException): | |
| """Handle AI service degradation.""" | |
| try: | |
| # Update AI service status | |
| service_name = error.context.get('service_name', 'unknown') | |
| if service_name in ai_service_manager.service_status: | |
| ai_service_manager.service_status[service_name] = False | |
| ai_service_manager._update_degradation_level() | |
| logger.info(f"AI service {service_name} marked as degraded") | |
| except Exception as e: | |
| logger.error(f"Failed to handle AI service degradation: {e}") | |
| def _execute_error_callbacks(self, error: DigiPalException): | |
| """Execute registered error callbacks.""" | |
| try: | |
| category_callbacks = self.error_callbacks.get(error.category.value, []) | |
| general_callbacks = self.error_callbacks.get('all', []) | |
| all_callbacks = category_callbacks + general_callbacks | |
| for callback in all_callbacks: | |
| try: | |
| callback(error) | |
| except Exception as callback_error: | |
| logger.error(f"Error callback failed: {callback_error}") | |
| except Exception as e: | |
| logger.error(f"Failed to execute error callbacks: {e}") | |
| async def handle_error_async( | |
| self, | |
| error: Exception, | |
| context: Optional[Dict[str, Any]] = None, | |
| attempt_recovery: bool = True, | |
| provide_fallback: bool = True, | |
| fallback_value: Any = None | |
| ) -> ErrorHandlingResult: | |
| """ | |
| Asynchronous comprehensive error handling. | |
| Args: | |
| error: The error that occurred | |
| context: Additional context information | |
| attempt_recovery: Whether to attempt automatic recovery | |
| provide_fallback: Whether to provide fallback functionality | |
| fallback_value: Fallback value to return | |
| Returns: | |
| ErrorHandlingResult with complete handling information | |
| """ | |
| # Run synchronous error handling in thread pool | |
| loop = asyncio.get_event_loop() | |
| return await loop.run_in_executor( | |
| None, | |
| self.handle_error_comprehensive, | |
| error, | |
| context, | |
| attempt_recovery, | |
| provide_fallback, | |
| fallback_value | |
| ) | |
| def create_error_safe_wrapper( | |
| self, | |
| func: Callable, | |
| fallback_value: Any = None, | |
| retry_config: Optional[RetryConfig] = None, | |
| context: Optional[Dict[str, Any]] = None | |
| ) -> Callable: | |
| """ | |
| Create an error-safe wrapper for any function. | |
| Args: | |
| func: Function to wrap | |
| fallback_value: Value to return on error | |
| retry_config: Retry configuration | |
| context: Additional context | |
| Returns: | |
| Error-safe wrapped function | |
| """ | |
| def wrapper(*args, **kwargs): | |
| try: | |
| # Apply retry if configured | |
| if retry_config: | |
| def retryable_func(): | |
| return func(*args, **kwargs) | |
| return retryable_func() | |
| else: | |
| return func(*args, **kwargs) | |
| except Exception as e: | |
| result = self.handle_error_comprehensive( | |
| e, | |
| context=context, | |
| fallback_value=fallback_value | |
| ) | |
| if result.success or result.fallback_value is not None: | |
| return result.fallback_value | |
| else: | |
| raise result.original_error | |
| return wrapper | |
| def get_system_health_status(self) -> Dict[str, Any]: | |
| """ | |
| Get comprehensive system health status. | |
| Returns: | |
| System health information | |
| """ | |
| try: | |
| from .error_handler import get_error_statistics, health_checker | |
| # Get error statistics | |
| error_stats = get_error_statistics() | |
| # Get AI service status | |
| ai_status = ai_service_manager.get_service_status() | |
| # Get backup status | |
| backup_stats = {} | |
| if self.backup_manager: | |
| backup_stats = self.backup_manager.get_backup_statistics() | |
| # Get health check status | |
| health_status = health_checker.get_system_health() | |
| # Calculate overall health score | |
| health_score = self._calculate_health_score(error_stats, ai_status, health_status) | |
| return { | |
| 'overall_health_score': health_score, | |
| 'status': 'healthy' if health_score > 0.8 else 'degraded' if health_score > 0.5 else 'critical', | |
| 'error_statistics': error_stats, | |
| 'ai_service_status': ai_status, | |
| 'backup_statistics': backup_stats, | |
| 'component_health': health_status, | |
| 'timestamp': datetime.now().isoformat() | |
| } | |
| except Exception as e: | |
| logger.error(f"Failed to get system health status: {e}") | |
| return { | |
| 'overall_health_score': 0.0, | |
| 'status': 'unknown', | |
| 'error': str(e), | |
| 'timestamp': datetime.now().isoformat() | |
| } | |
| def _calculate_health_score( | |
| self, | |
| error_stats: Dict[str, Any], | |
| ai_status: Dict[str, Any], | |
| health_status: Dict[str, Any] | |
| ) -> float: | |
| """Calculate overall system health score (0.0 to 1.0).""" | |
| try: | |
| score = 1.0 | |
| # Reduce score based on error rate | |
| error_rate = error_handler.get_error_rate(window_minutes=5) | |
| if error_rate > 0: | |
| score -= min(error_rate * 0.1, 0.5) # Max 50% reduction for errors | |
| # Reduce score based on AI service degradation | |
| degradation_level = ai_status.get('degradation_level', 'full_service') | |
| degradation_penalties = { | |
| 'full_service': 0.0, | |
| 'reduced_features': 0.1, | |
| 'basic_responses': 0.3, | |
| 'minimal_function': 0.5, | |
| 'emergency_mode': 0.7 | |
| } | |
| score -= degradation_penalties.get(degradation_level, 0.5) | |
| # Reduce score based on component health | |
| if health_status.get('overall_health') == 'critical': | |
| score -= 0.4 | |
| elif health_status.get('overall_health') == 'unhealthy': | |
| score -= 0.2 | |
| elif health_status.get('overall_health') == 'degraded': | |
| score -= 0.1 | |
| return max(0.0, min(1.0, score)) | |
| except Exception as e: | |
| logger.error(f"Failed to calculate health score: {e}") | |
| return 0.5 # Default to moderate health | |
| def generate_health_report(self) -> str: | |
| """ | |
| Generate a human-readable health report. | |
| Returns: | |
| Health report string | |
| """ | |
| try: | |
| health_status = self.get_system_health_status() | |
| report_lines = [ | |
| "=== DigiPal System Health Report ===", | |
| f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", | |
| "", | |
| f"Overall Status: {health_status['status'].upper()}", | |
| f"Health Score: {health_status['overall_health_score']:.2f}/1.00", | |
| "" | |
| ] | |
| # Error statistics | |
| error_stats = health_status.get('error_statistics', {}) | |
| if error_stats.get('error_counts'): | |
| report_lines.extend([ | |
| "Recent Errors:", | |
| f" - Total error types: {len(error_stats['error_counts'])}", | |
| f" - Most frequent: {list(error_stats['error_counts'].keys())[:3]}" | |
| ]) | |
| else: | |
| report_lines.append("Recent Errors: None") | |
| report_lines.append("") | |
| # AI service status | |
| ai_status = health_status.get('ai_service_status', {}) | |
| degradation_level = ai_status.get('degradation_level', 'unknown') | |
| report_lines.extend([ | |
| f"AI Services: {degradation_level.replace('_', ' ').title()}", | |
| f" - Language Model: {'✓' if ai_status.get('services', {}).get('language_model') else '✗'}", | |
| f" - Speech Processing: {'✓' if ai_status.get('services', {}).get('speech_processing') else '✗'}", | |
| f" - Image Generation: {'✓' if ai_status.get('services', {}).get('image_generation') else '✗'}", | |
| "" | |
| ]) | |
| # Backup status | |
| backup_stats = health_status.get('backup_statistics', {}) | |
| if backup_stats: | |
| report_lines.extend([ | |
| f"Backup System:", | |
| f" - Total backups: {backup_stats.get('total_backups', 0)}", | |
| f" - Storage used: {backup_stats.get('total_size_bytes', 0) / 1024 / 1024:.1f} MB", | |
| "" | |
| ]) | |
| # Recommendations | |
| recommendations = self._generate_health_recommendations(health_status) | |
| if recommendations: | |
| report_lines.extend([ | |
| "Recommendations:", | |
| *[f" - {rec}" for rec in recommendations], | |
| "" | |
| ]) | |
| report_lines.append("=== End of Report ===") | |
| return "\n".join(report_lines) | |
| except Exception as e: | |
| logger.error(f"Failed to generate health report: {e}") | |
| return f"Health report generation failed: {str(e)}" | |
| def _generate_health_recommendations(self, health_status: Dict[str, Any]) -> List[str]: | |
| """Generate health recommendations based on system status.""" | |
| recommendations = [] | |
| try: | |
| health_score = health_status.get('overall_health_score', 1.0) | |
| if health_score < 0.5: | |
| recommendations.append("System health is critical - consider restarting the application") | |
| # AI service recommendations | |
| ai_status = health_status.get('ai_service_status', {}) | |
| degradation_level = ai_status.get('degradation_level', 'full_service') | |
| if degradation_level != 'full_service': | |
| recommendations.append("AI services are degraded - check internet connection and model availability") | |
| # Error rate recommendations | |
| error_stats = health_status.get('error_statistics', {}) | |
| if error_stats.get('error_counts'): | |
| most_frequent = list(error_stats['error_counts'].keys())[:1] | |
| if most_frequent: | |
| error_type = most_frequent[0].split(':')[0] | |
| recommendations.append(f"High {error_type} error rate - check system logs for details") | |
| # Backup recommendations | |
| backup_stats = health_status.get('backup_statistics', {}) | |
| if backup_stats.get('total_backups', 0) == 0: | |
| recommendations.append("No backups found - enable automatic backups for data safety") | |
| except Exception as e: | |
| logger.error(f"Failed to generate recommendations: {e}") | |
| recommendations.append("Unable to generate specific recommendations - check system logs") | |
| return recommendations | |
| # Global unified error handler instance | |
| unified_error_handler: Optional[DigiPalErrorHandler] = None | |
| def initialize_error_handling(backup_manager: Optional[BackupRecoveryManager] = None): | |
| """ | |
| Initialize the global unified error handler. | |
| Args: | |
| backup_manager: Backup manager for recovery operations | |
| """ | |
| global unified_error_handler | |
| unified_error_handler = DigiPalErrorHandler(backup_manager) | |
| logger.info("Global unified error handler initialized") | |
| def get_error_handler() -> Optional[DigiPalErrorHandler]: | |
| """Get the global unified error handler.""" | |
| return unified_error_handler | |
| def handle_error_safely( | |
| error: Exception, | |
| context: Optional[Dict[str, Any]] = None, | |
| fallback_value: Any = None | |
| ) -> ErrorHandlingResult: | |
| """ | |
| Safely handle an error using the global error handler. | |
| Args: | |
| error: The error that occurred | |
| context: Additional context | |
| fallback_value: Fallback value | |
| Returns: | |
| ErrorHandlingResult | |
| """ | |
| if unified_error_handler: | |
| return unified_error_handler.handle_error_comprehensive( | |
| error, context, fallback_value=fallback_value | |
| ) | |
| else: | |
| # Fallback to basic error handling | |
| digipal_error = error_handler.handle_error(error, context) | |
| return ErrorHandlingResult( | |
| success=False, | |
| original_error=digipal_error, | |
| user_message="An error occurred. Please try again.", | |
| recovery_attempted=False, | |
| recovery_result=None, | |
| recovery_guide=[], | |
| fallback_value=fallback_value, | |
| context=context or {} | |
| ) | |
| def create_safe_function( | |
| func: Callable, | |
| fallback_value: Any = None, | |
| context: Optional[Dict[str, Any]] = None | |
| ) -> Callable: | |
| """ | |
| Create a safe version of any function. | |
| Args: | |
| func: Function to make safe | |
| fallback_value: Value to return on error | |
| context: Additional context | |
| Returns: | |
| Safe function wrapper | |
| """ | |
| if unified_error_handler: | |
| return unified_error_handler.create_error_safe_wrapper( | |
| func, fallback_value, context=context | |
| ) | |
| else: | |
| # Basic wrapper | |
| def safe_wrapper(*args, **kwargs): | |
| try: | |
| return func(*args, **kwargs) | |
| except Exception as e: | |
| logger.error(f"Function {func.__name__} failed: {e}") | |
| return fallback_value | |
| return safe_wrapper |