|
""" |
|
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) |
|
|
|
|
|
if rules.get('required', False) and value is None: |
|
raise ValidationError(f"Field '{field}' is required", field=field, value=value) |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
|
|
return False |