mrradix commited on
Commit
65e7d4d
·
verified ·
1 Parent(s): e741c5a

Update utils/error_handling.py

Browse files
Files changed (1) hide show
  1. utils/error_handling.py +221 -280
utils/error_handling.py CHANGED
@@ -1,336 +1,277 @@
1
  """
2
- Error handling utilities for the application.
 
 
 
3
  """
4
 
5
- import functools
6
  import traceback
7
- from typing import Any, Callable, Dict, Optional, Type, Union
 
8
  from utils.logging import get_logger, log_error
9
 
10
- # Custom Exception Classes
11
- class DataError(Exception):
12
- """Base exception for data-related errors."""
13
- def __init__(self, message: str, code: str = None, details: Dict[str, Any] = None):
14
- super().__init__(message)
15
- self.code = code
16
- self.details = details or {}
17
-
18
- class ValidationError(DataError):
19
- """Exception raised when data validation fails."""
20
- def __init__(self, message: str, field: str = None, value: Any = None, code: str = "VALIDATION_ERROR"):
21
- super().__init__(message, code)
22
- self.field = field
23
- self.value = value
24
- if field:
25
- self.details["field"] = field
26
- if value is not None:
27
- self.details["value"] = value
28
-
29
- class StorageError(DataError):
30
- """Exception raised for storage-related errors."""
31
- def __init__(self, message: str, operation: str = None, code: str = "STORAGE_ERROR"):
32
- super().__init__(message, code)
33
- self.operation = operation
34
- if operation:
35
- self.details["operation"] = operation
36
-
37
- class ConfigurationError(Exception):
38
- """Exception raised for configuration-related errors."""
39
- def __init__(self, message: str, config_key: str = None):
40
- super().__init__(message)
41
- self.config_key = config_key
42
-
43
- class APIError(Exception):
44
- """Exception raised for API-related errors."""
45
- def __init__(self, message: str, status_code: int = None, response_data: Dict = None):
46
- super().__init__(message)
47
- self.status_code = status_code
48
- self.response_data = response_data or {}
49
-
50
- # Error Handler Decorator
51
- def handle_data_exceptions(
52
- default_return: Any = None,
53
- re_raise: bool = False,
54
- log_errors: bool = True,
55
- logger = None
56
- ):
57
  """
58
- Decorator to handle data-related exceptions.
59
 
60
  Args:
61
- default_return: Value to return if an exception occurs
62
- re_raise: Whether to re-raise the exception after handling
63
- log_errors: Whether to log errors
64
- logger: Logger instance to use
65
 
66
  Returns:
67
- Decorated function
68
  """
69
- def decorator(func: Callable) -> Callable:
70
- @functools.wraps(func)
71
- def wrapper(*args, **kwargs):
72
- try:
73
- return func(*args, **kwargs)
74
- except (DataError, ValidationError, StorageError) as e:
75
- if log_errors:
76
- error_logger = logger or get_logger()
77
- log_error(e, f"Data exception in {func.__name__}", error_logger)
78
-
79
- if re_raise:
80
- raise
81
- return default_return
82
- except Exception as e:
83
- if log_errors:
84
- error_logger = logger or get_logger()
85
- log_error(e, f"Unexpected exception in {func.__name__}", error_logger)
86
-
87
- if re_raise:
88
- raise
89
- return default_return
90
- return wrapper
91
- return decorator
92
-
93
- def handle_exceptions(
94
- exceptions: Union[Type[Exception], tuple] = Exception,
95
- default_return: Any = None,
96
- log_errors: bool = True,
97
- logger = None
98
- ):
 
 
99
  """
100
- Generic exception handler decorator.
101
 
102
  Args:
103
- exceptions: Exception type(s) to catch
104
- default_return: Value to return if an exception occurs
105
- log_errors: Whether to log errors
106
- logger: Logger instance to use
107
 
108
  Returns:
109
- Decorated function
110
  """
111
- def decorator(func: Callable) -> Callable:
112
- @functools.wraps(func)
113
- def wrapper(*args, **kwargs):
114
- try:
115
- return func(*args, **kwargs)
116
- except exceptions as e:
117
- if log_errors:
118
- error_logger = logger or get_logger()
119
- log_error(e, f"Exception in {func.__name__}", error_logger)
120
- return default_return
121
- return wrapper
122
- return decorator
123
-
124
- # Error Context Manager
125
- class ErrorHandler:
126
- """Context manager for handling errors."""
127
-
128
- def __init__(self,
129
- default_return: Any = None,
130
- log_errors: bool = True,
131
- logger = None,
132
- suppress: bool = True):
133
- self.default_return = default_return
134
- self.log_errors = log_errors
135
- self.logger = logger or get_logger()
136
- self.suppress = suppress
137
- self.exception = None
138
- self.return_value = None
139
-
140
- def __enter__(self):
141
- return self
142
-
143
- def __exit__(self, exc_type, exc_val, exc_tb):
144
- if exc_type is not None:
145
- self.exception = exc_val
146
-
147
- if self.log_errors:
148
- log_error(exc_val, "Exception in ErrorHandler context", self.logger)
149
-
150
- self.return_value = self.default_return
151
- return self.suppress
152
- return False
153
-
154
- # Validation Functions
155
- def validate_required_fields(data: Dict[str, Any], required_fields: list) -> None:
156
  """
157
- Validate that all required fields are present in data.
158
 
159
  Args:
160
- data: Dictionary to validate
161
- required_fields: List of required field names
162
 
163
- Raises:
164
- ValidationError: If any required field is missing
165
  """
166
- missing_fields = [field for field in required_fields if field not in data or data[field] is None]
167
-
168
- if missing_fields:
169
- raise ValidationError(
170
- f"Missing required fields: {', '.join(missing_fields)}",
171
- code="MISSING_REQUIRED_FIELDS"
172
- )
 
 
 
 
 
 
 
 
 
 
173
 
174
- def validate_data_types(data: Dict[str, Any], type_mapping: Dict[str, type]) -> None:
 
175
  """
176
- Validate data types for specified fields.
177
 
178
  Args:
179
- data: Dictionary to validate
180
- type_mapping: Dictionary mapping field names to expected types
181
 
182
- Raises:
183
- ValidationError: If any field has incorrect type
184
  """
185
- for field, expected_type in type_mapping.items():
186
- if field in data and data[field] is not None:
187
- if not isinstance(data[field], expected_type):
188
- raise ValidationError(
189
- f"Field '{field}' must be of type {expected_type.__name__}, got {type(data[field]).__name__}",
190
- field=field,
191
- value=data[field],
192
- code="INVALID_TYPE"
193
- )
194
-
195
- def validate_field_length(data: Dict[str, Any], length_constraints: Dict[str, Dict[str, int]]) -> None:
 
 
 
 
196
  """
197
- Validate field lengths against constraints.
198
 
199
  Args:
200
- data: Dictionary to validate
201
- length_constraints: Dictionary mapping field names to length constraints
202
- e.g., {"name": {"min": 1, "max": 100}}
203
 
204
  Raises:
205
- ValidationError: If any field violates length constraints
206
  """
207
- for field, constraints in length_constraints.items():
 
 
 
 
 
 
 
 
208
  if field in data and data[field] is not None:
209
- value = data[field]
210
- if hasattr(value, '__len__'):
211
- length = len(value)
212
-
213
- if 'min' in constraints and length < constraints['min']:
214
- raise ValidationError(
215
- f"Field '{field}' must be at least {constraints['min']} characters long",
216
- field=field,
217
- value=value,
218
- code="TOO_SHORT"
219
- )
220
-
221
- if 'max' in constraints and length > constraints['max']:
222
- raise ValidationError(
223
- f"Field '{field}' must be at most {constraints['max']} characters long",
224
- field=field,
225
- value=value,
226
- code="TOO_LONG"
227
- )
228
-
229
- # Error Reporting
230
- def format_error_response(error: Exception, include_traceback: bool = False) -> Dict[str, Any]:
 
 
231
  """
232
- Format an error for API response.
233
 
234
  Args:
235
- error: Exception instance
236
- include_traceback: Whether to include traceback
 
237
 
238
  Returns:
239
- Dictionary containing error information
240
  """
241
- response = {
242
- "error": True,
243
- "error_type": type(error).__name__,
244
- "message": str(error)
245
- }
246
-
247
- # Add custom error details
248
- if hasattr(error, 'code'):
249
- response["code"] = error.code
250
-
251
- if hasattr(error, 'details'):
252
- response["details"] = error.details
253
 
254
- if hasattr(error, 'status_code'):
255
- response["status_code"] = error.status_code
256
-
257
- if include_traceback:
258
- response["traceback"] = traceback.format_exc()
259
-
260
- return response
 
 
261
 
262
- def safe_execute(func: Callable, *args, default_return: Any = None, **kwargs) -> Any:
 
 
263
  """
264
- Safely execute a function and return default value on error.
265
 
266
  Args:
267
- func: Function to execute
268
- *args: Positional arguments for the function
269
- default_return: Value to return on error
270
- **kwargs: Keyword arguments for the function
271
 
272
  Returns:
273
- Function result or default_return on error
274
  """
275
- try:
276
- return func(*args, **kwargs)
277
- except Exception as e:
278
- logger = get_logger()
279
- log_error(e, f"Error in safe_execute for {func.__name__}", logger)
280
- return default_return
281
-
282
- # Retry mechanism
283
- def retry_on_exception(
284
- max_attempts: int = 3,
285
- delay: float = 1.0,
286
- exceptions: tuple = (Exception,),
287
- backoff_factor: float = 2.0
288
- ):
289
  """
290
- Decorator to retry function execution on specified exceptions.
291
 
292
  Args:
293
- max_attempts: Maximum number of retry attempts
294
- delay: Initial delay between retries (seconds)
295
- exceptions: Tuple of exception types to retry on
296
- backoff_factor: Factor to multiply delay by for each retry
297
 
298
  Returns:
299
- Decorated function
300
  """
301
- def decorator(func: Callable) -> Callable:
302
- @functools.wraps(func)
303
- def wrapper(*args, **kwargs):
304
- last_exception = None
305
- current_delay = delay
306
-
307
- for attempt in range(max_attempts):
308
- try:
309
- return func(*args, **kwargs)
310
- except exceptions as e:
311
- last_exception = e
312
-
313
- if attempt < max_attempts - 1: # Don't sleep on last attempt
314
- logger = get_logger()
315
- logger.warning(f"Attempt {attempt + 1} failed for {func.__name__}: {e}. Retrying in {current_delay}s...")
316
-
317
- import time
318
- time.sleep(current_delay)
319
- current_delay *= backoff_factor
320
- else:
321
- logger = get_logger()
322
- log_error(e, f"All {max_attempts} attempts failed for {func.__name__}", logger)
323
-
324
- # Re-raise the last exception if all attempts failed
325
- raise last_exception
326
- return wrapper
327
- return decorator
328
- def safe_get(obj, key, default=None):
329
- """Safely get a value from dict/object with fallback."""
330
- try:
331
- if isinstance(obj, dict):
332
- return obj.get(key, default)
333
- else:
334
- return getattr(obj, key, default)
335
- except (AttributeError, KeyError, TypeError):
336
- return default
 
1
  """
2
+ Error handling module for the MONA application.
3
+
4
+ This module provides standardized error handling patterns and custom exceptions
5
+ for consistent error management across the application.
6
  """
7
 
 
8
  import traceback
9
+ from functools import wraps
10
+
11
  from utils.logging import get_logger, log_error
12
 
13
+ # Initialize logger
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ # Custom exceptions
18
+ class MonaError(Exception):
19
+ """Base exception class for all MONA application errors."""
20
+ def __init__(self, message, details=None):
21
+ self.message = message
22
+ self.details = details
23
+ super().__init__(self.message)
24
+
25
+
26
+ class DataError(MonaError):
27
+ """Exception raised for errors related to data operations."""
28
+ pass
29
+
30
+
31
+ class AIModelError(MonaError):
32
+ """Exception raised for errors related to AI model operations."""
33
+ pass
34
+
35
+
36
+ class ValidationError(MonaError):
37
+ """Exception raised for validation errors."""
38
+ pass
39
+
40
+
41
+ class IntegrationError(MonaError):
42
+ """Exception raised for errors related to external integrations."""
43
+ pass
44
+
45
+
46
+ class ConfigurationError(MonaError):
47
+ """Exception raised for configuration errors."""
48
+ pass
49
+
50
+
51
+ class UserInputError(MonaError):
52
+ """Exception raised for invalid user input."""
53
+ pass
54
+
55
+
56
+ # Error handling decorators
57
+ def handle_exceptions(func):
 
 
58
  """
59
+ Decorator to handle exceptions in a standardized way.
60
 
61
  Args:
62
+ func (callable): The function to decorate.
 
 
 
63
 
64
  Returns:
65
+ callable: The decorated function.
66
  """
67
+ @wraps(func)
68
+ def wrapper(*args, **kwargs):
69
+ try:
70
+ return func(*args, **kwargs)
71
+ except MonaError as e:
72
+ # Log the custom exception
73
+ log_error(f"{type(e).__name__}: {e.message}", exc_info=e)
74
+ # Return a user-friendly error message
75
+ return {
76
+ "success": False,
77
+ "error": {
78
+ "type": type(e).__name__,
79
+ "message": e.message,
80
+ "details": e.details
81
+ }
82
+ }
83
+ except Exception as e:
84
+ # Log unexpected exceptions
85
+ log_error(f"Unexpected error in {func.__name__}: {str(e)}", exc_info=e)
86
+ # Return a generic error message
87
+ return {
88
+ "success": False,
89
+ "error": {
90
+ "type": "UnexpectedError",
91
+ "message": "An unexpected error occurred. Please try again later.",
92
+ "details": str(e) if __debug__ else None
93
+ }
94
+ }
95
+ return wrapper
96
+
97
+
98
+ def handle_ui_exceptions(func):
99
  """
100
+ Decorator to handle exceptions in UI components.
101
 
102
  Args:
103
+ func (callable): The function to decorate.
 
 
 
104
 
105
  Returns:
106
+ callable: The decorated function.
107
  """
108
+ @wraps(func)
109
+ def wrapper(*args, **kwargs):
110
+ try:
111
+ return func(*args, **kwargs)
112
+ except MonaError as e:
113
+ # Log the custom exception
114
+ log_error(f"UI Error - {type(e).__name__}: {e.message}", exc_info=e)
115
+ # Return a user-friendly error message for UI display
116
+ return f"Error: {e.message}"
117
+ except Exception as e:
118
+ # Log unexpected exceptions
119
+ log_error(f"Unexpected UI error in {func.__name__}: {str(e)}", exc_info=e)
120
+ # Return a generic error message for UI display
121
+ return "An unexpected error occurred. Please try again later."
122
+ return wrapper
123
+
124
+
125
+ def handle_data_exceptions(func):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  """
127
+ Decorator to handle exceptions in data operations.
128
 
129
  Args:
130
+ func (callable): The function to decorate.
 
131
 
132
+ Returns:
133
+ callable: The decorated function.
134
  """
135
+ @wraps(func)
136
+ def wrapper(*args, **kwargs):
137
+ try:
138
+ return func(*args, **kwargs)
139
+ except (FileNotFoundError, PermissionError, IOError) as e:
140
+ # Convert to custom exception
141
+ raise DataError(f"Data access error: {str(e)}", {
142
+ "original_error": str(e),
143
+ "traceback": traceback.format_exc()
144
+ }) from e
145
+ except Exception as e:
146
+ # Convert to custom exception
147
+ raise DataError(f"Unexpected data error: {str(e)}", {
148
+ "original_error": str(e),
149
+ "traceback": traceback.format_exc()
150
+ }) from e
151
+ return wrapper
152
 
153
+
154
+ def handle_ai_model_exceptions(func):
155
  """
156
+ Decorator to handle exceptions in AI model operations.
157
 
158
  Args:
159
+ func (callable): The function to decorate.
 
160
 
161
+ Returns:
162
+ callable: The decorated function.
163
  """
164
+ @wraps(func)
165
+ def wrapper(*args, **kwargs):
166
+ try:
167
+ return func(*args, **kwargs)
168
+ except Exception as e:
169
+ # Convert to custom exception
170
+ raise AIModelError(f"AI model error: {str(e)}", {
171
+ "original_error": str(e),
172
+ "traceback": traceback.format_exc()
173
+ }) from e
174
+ return wrapper
175
+
176
+
177
+ # Validation functions
178
+ def validate_input(data, schema):
179
  """
180
+ Validate input data against a schema.
181
 
182
  Args:
183
+ data (dict): The input data to validate.
184
+ schema (dict): The schema to validate against.
 
185
 
186
  Raises:
187
+ ValidationError: If validation fails.
188
  """
189
+ errors = []
190
+
191
+ # Check required fields
192
+ for field in schema.get("required", []):
193
+ if field not in data:
194
+ errors.append(f"Missing required field: {field}")
195
+
196
+ # Check field types
197
+ for field, field_schema in schema.get("properties", {}).items():
198
  if field in data and data[field] is not None:
199
+ expected_type = field_schema.get("type")
200
+ if expected_type == "string" and not isinstance(data[field], str):
201
+ errors.append(f"Field {field} must be a string")
202
+ elif expected_type == "number" and not isinstance(data[field], (int, float)):
203
+ errors.append(f"Field {field} must be a number")
204
+ elif expected_type == "integer" and not isinstance(data[field], int):
205
+ errors.append(f"Field {field} must be an integer")
206
+ elif expected_type == "boolean" and not isinstance(data[field], bool):
207
+ errors.append(f"Field {field} must be a boolean")
208
+ elif expected_type == "array" and not isinstance(data[field], list):
209
+ errors.append(f"Field {field} must be an array")
210
+ elif expected_type == "object" and not isinstance(data[field], dict):
211
+ errors.append(f"Field {field} must be an object")
212
+
213
+ if errors:
214
+ raise ValidationError("Validation failed", {"errors": errors})
215
+
216
+
217
+ def safe_get(dictionary, key, default=None):
218
+ """Safely get a value from dict/object with fallback."""
219
+ try:
220
+ return dictionary[key]
221
+ except KeyError:
222
+ return default
223
  """
224
+ Safely get a value from a nested dictionary.
225
 
226
  Args:
227
+ data (dict): The dictionary to get the value from.
228
+ path (str): The path to the value, using dot notation (e.g., "user.profile.name").
229
+ default: The default value to return if the path doesn't exist.
230
 
231
  Returns:
232
+ The value at the specified path, or the default value if the path doesn't exist.
233
  """
234
+ keys = path.split('.')
235
+ result = data
 
 
 
 
 
 
 
 
 
 
236
 
237
+ try:
238
+ for key in keys:
239
+ if isinstance(result, dict):
240
+ result = result.get(key, default)
241
+ else:
242
+ return default
243
+ return result
244
+ except Exception:
245
+ return default
246
 
247
+
248
+ # Error formatting functions
249
+ def format_error_for_user(error):
250
  """
251
+ Format an error for user display.
252
 
253
  Args:
254
+ error (Exception): The error to format.
 
 
 
255
 
256
  Returns:
257
+ str: A user-friendly error message.
258
  """
259
+ if isinstance(error, MonaError):
260
+ return error.message
261
+ return "An unexpected error occurred. Please try again later."
262
+
263
+
264
+ def format_error_for_log(error):
 
 
 
 
 
 
 
 
265
  """
266
+ Format an error for logging.
267
 
268
  Args:
269
+ error (Exception): The error to format.
 
 
 
270
 
271
  Returns:
272
+ str: A detailed error message for logging.
273
  """
274
+ if isinstance(error, MonaError):
275
+ details = error.details if error.details else {}
276
+ return f"{type(error).__name__}: {error.message} - {details}"
277
+ return f"{type(error).__name__}: {str(error)}\n{traceback.format_exc()}"