""" Error handling utilities for the MONA application Fixed version with proper exception handling and logging """ import functools import traceback from typing import Any, Callable, Dict, Optional, Union from utils.logging import get_logger, log_error, log_warning class DataError(Exception): """Custom exception for data-related errors""" def __init__(self, message: str, error_code: str = None, details: Dict = None): self.message = message self.error_code = error_code or "DATA_ERROR" self.details = details or {} super().__init__(self.message) class ValidationError(Exception): """Custom exception for validation errors""" def __init__(self, message: str, field: str = None, value: Any = None): self.message = message self.field = field self.value = value super().__init__(self.message) class ProcessingError(Exception): """Custom exception for processing errors""" def __init__(self, message: str, step: str = None, context: Dict = None): self.message = message self.step = step self.context = context or {} super().__init__(self.message) def handle_data_exceptions(func: Callable) -> Callable: """ Decorator to handle data-related exceptions Args: func: Function to wrap with exception handling Returns: Wrapped function with exception handling """ @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except DataError as e: log_error(f"Data error in {func.__name__}: {e.message}", error=e, extra_data={"error_code": e.error_code, "details": e.details}) raise except ValidationError as e: log_error(f"Validation error in {func.__name__}: {e.message}", error=e, extra_data={"field": e.field, "value": e.value}) raise except ProcessingError as e: log_error(f"Processing error in {func.__name__}: {e.message}", error=e, extra_data={"step": e.step, "context": e.context}) raise except Exception as e: log_error(f"Unexpected error in {func.__name__}: {str(e)}", error=e) raise ProcessingError(f"Unexpected error in {func.__name__}", context={"original_error": str(e)}) return wrapper def safe_execute(func: Callable, *args, default_return=None, **kwargs) -> tuple: """ Safely execute a function and return success status with result Args: func: Function to execute *args: Positional arguments for the function default_return: Default value to return on error **kwargs: Keyword arguments for the function Returns: tuple: (success: bool, result: Any, error: Optional[Exception]) """ try: result = func(*args, **kwargs) return True, result, None except Exception as e: log_error(f"Error executing {func.__name__}: {str(e)}", error=e) return False, default_return, e def validate_data(data: Any, validation_rules: Dict) -> bool: """ Validate data against a set of rules Args: data: Data to validate validation_rules: Dictionary of validation rules Returns: bool: True if validation passes Raises: ValidationError: If validation fails """ if not isinstance(validation_rules, dict): raise ValidationError("Validation rules must be a dictionary") for field, rules in validation_rules.items(): if isinstance(data, dict): value = data.get(field) else: value = getattr(data, field, None) # Check required fields if rules.get('required', False) and value is None: raise ValidationError(f"Field '{field}' is required", field=field, value=value) # Check data type if 'type' in rules and value is not None: expected_type = rules['type'] if not isinstance(value, expected_type): raise ValidationError( f"Field '{field}' must be of type {expected_type.__name__}, got {type(value).__name__}", field=field, value=value ) # Check value range for numbers if 'min_value' in rules and isinstance(value, (int, float)): if value < rules['min_value']: raise ValidationError( f"Field '{field}' must be >= {rules['min_value']}, got {value}", field=field, value=value ) if 'max_value' in rules and isinstance(value, (int, float)): if value > rules['max_value']: raise ValidationError( f"Field '{field}' must be <= {rules['max_value']}, got {value}", field=field, value=value ) # Check string length if 'min_length' in rules and isinstance(value, str): if len(value) < rules['min_length']: raise ValidationError( f"Field '{field}' must be at least {rules['min_length']} characters", field=field, value=value ) if 'max_length' in rules and isinstance(value, str): if len(value) > rules['max_length']: raise ValidationError( f"Field '{field}' must be at most {rules['max_length']} characters", field=field, value=value ) return True def format_error_response(error: Exception) -> Dict: """ Format an error into a standardized response Args: error: Exception to format Returns: Dict: Formatted error response """ if isinstance(error, (DataError, ValidationError, ProcessingError)): return { "success": False, "error_type": error.__class__.__name__, "message": error.message, "details": getattr(error, 'details', {}), "field": getattr(error, 'field', None), "error_code": getattr(error, 'error_code', None) } else: return { "success": False, "error_type": "UnexpectedError", "message": str(error), "details": {} } def retry_on_failure(max_retries: int = 3, delay: float = 1.0, exceptions: tuple = (Exception,)): """ Decorator to retry function execution on failure Args: max_retries: Maximum number of retry attempts delay: Delay between retries in seconds exceptions: Tuple of exceptions to catch and retry on Returns: Decorator function """ def decorator(func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs): import time last_exception = None for attempt in range(max_retries + 1): try: return func(*args, **kwargs) except exceptions as e: last_exception = e if attempt < max_retries: log_warning(f"Attempt {attempt + 1} failed for {func.__name__}: {str(e)}. Retrying in {delay}s...") time.sleep(delay) else: log_error(f"All {max_retries + 1} attempts failed for {func.__name__}", error=e) raise last_exception return wrapper return decorator # Context manager for error handling class ErrorHandler: """Context manager for comprehensive error handling""" def __init__(self, operation_name: str, raise_on_error: bool = True): self.operation_name = operation_name self.raise_on_error = raise_on_error self.error = None def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: self.error = exc_val log_error(f"Error in {self.operation_name}: {str(exc_val)}", error=exc_val) if not self.raise_on_error: return True # Suppress the exception return False # Let the exception propagate