mrradix commited on
Commit
8be45a9
·
verified ·
1 Parent(s): 141cd60

Update utils/error_handling.py

Browse files
Files changed (1) hide show
  1. utils/error_handling.py +220 -111
utils/error_handling.py CHANGED
@@ -1,147 +1,256 @@
1
- import traceback
 
 
 
 
2
  import functools
3
- from typing import Any, Callable
 
 
 
4
 
5
- # Import your logging utilities
6
- from utils.logging import get_logger, log_error
7
 
8
- # Custom exception classes
9
  class DataError(Exception):
10
  """Custom exception for data-related errors"""
11
- pass
 
 
 
 
 
12
 
13
  class ValidationError(Exception):
14
  """Custom exception for validation errors"""
15
- pass
 
 
 
 
16
 
17
- class AIModelError(Exception):
18
- """Custom exception for AI model errors"""
19
- def __init__(self, message, metadata=None):
20
- super().__init__(message)
21
- self.metadata = metadata or {}
22
 
23
- # Get logger
24
- logger = get_logger(__name__)
 
 
 
 
 
 
25
 
26
  def handle_data_exceptions(func: Callable) -> Callable:
27
  """
28
- Decorator to handle data-related exceptions gracefully
 
 
 
 
 
 
29
  """
30
  @functools.wraps(func)
31
  def wrapper(*args, **kwargs):
32
  try:
33
  return func(*args, **kwargs)
34
- except (DataError, ValidationError) as e:
35
- log_error(logger, f"Data handling error in {func.__name__}: {str(e)}")
36
- print(f"Data Error: {str(e)}")
37
- return None
 
 
 
 
 
 
 
 
 
 
 
38
  except Exception as e:
39
- log_error(logger, f"Unexpected error in {func.__name__}: {str(e)}")
40
- log_error(logger, f"Traceback: {traceback.format_exc()}")
41
- print(f"An unexpected error occurred: {str(e)}")
42
- return None
 
43
  return wrapper
44
 
45
- def handle_ai_model_exceptions(func: Callable) -> Callable:
46
- """
47
- Decorator to handle AI model-related exceptions gracefully
48
- """
49
- @functools.wraps(func)
50
- def wrapper(*args, **kwargs):
51
- try:
52
- return func(*args, **kwargs)
53
- except (AIModelError, ValidationError) as e:
54
- log_error(logger, f"AI model error in {func.__name__}: {str(e)}")
55
- print(f"AI Model Error: {str(e)}")
56
- return None
57
- except Exception as e:
58
- log_error(logger, f"Unexpected AI model error in {func.__name__}: {str(e)}")
59
- log_error(logger, f"Traceback: {traceback.format_exc()}")
60
- print(f"An unexpected AI model error occurred: {str(e)}")
61
- return None
62
- return wrapper
63
 
64
- def handle_exceptions(func: Callable) -> Callable:
65
  """
66
- General exception handler decorator
 
 
 
 
 
 
 
 
 
67
  """
68
- @functools.wraps(func)
69
- def wrapper(*args, **kwargs):
70
- try:
71
- return func(*args, **kwargs)
72
- except Exception as e:
73
- log_error(logger, f"Exception in {func.__name__}: {str(e)}")
74
- log_error(logger, f"Traceback: {traceback.format_exc()}")
75
- return None
76
- return wrapper
77
 
78
- def validate_data(data: Any, data_type: str) -> bool:
 
79
  """
80
- Validate data based on expected type
 
 
 
 
 
 
 
 
 
 
81
  """
82
- if data is None:
83
- raise ValidationError(f"Data cannot be None for type: {data_type}")
84
-
85
- if data_type == "dataframe":
86
- try:
87
- import pandas as pd
88
- if not isinstance(data, pd.DataFrame):
89
- raise ValidationError("Expected pandas DataFrame")
90
- if data.empty:
91
- raise ValidationError("DataFrame cannot be empty")
92
- except ImportError:
93
- raise ValidationError("pandas not available for DataFrame validation")
94
-
95
- elif data_type == "list":
96
- if not isinstance(data, list):
97
- raise ValidationError("Expected list")
98
- if len(data) == 0:
99
- raise ValidationError("List cannot be empty")
100
-
101
- elif data_type == "dict":
102
- if not isinstance(data, dict):
103
- raise ValidationError("Expected dictionary")
104
- if len(data) == 0:
105
- raise ValidationError("Dictionary cannot be empty")
106
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  return True
108
 
109
- def show_error(message: str):
110
- """
111
- Display error message (streamlit-safe)
112
- """
113
- try:
114
- import streamlit as st
115
- st.error(message)
116
- except ImportError:
117
- print(f"ERROR: {message}")
118
 
119
- def show_warning(message: str):
120
  """
121
- Display warning message (streamlit-safe)
 
 
 
 
 
 
122
  """
123
- try:
124
- import streamlit as st
125
- st.warning(message)
126
- except ImportError:
127
- print(f"WARNING: {message}")
 
 
 
 
 
 
 
 
 
 
 
128
 
129
- def show_success(message: str):
130
- """
131
- Display success message (streamlit-safe)
132
- """
133
- try:
134
- import streamlit as st
135
- st.success(message)
136
- except ImportError:
137
- print(f"SUCCESS: {message}")
138
 
139
- def safe_get(dictionary: dict, key: str, default: Any = None) -> Any:
140
  """
141
- Safely get value from dictionary
 
 
 
 
 
 
 
 
142
  """
143
- try:
144
- return dictionary.get(key, default)
145
- except (AttributeError, TypeError):
146
- logger.warning(f"safe_get failed for key '{key}' - dictionary is not dict-like")
147
- return default
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Error handling utilities for the MONA application
3
+ Fixed version with proper exception handling and logging
4
+ """
5
+
6
  import functools
7
+ import traceback
8
+ from typing import Any, Callable, Dict, Optional, Union
9
+
10
+ from utils.logging import get_logger, log_error, log_warning
11
 
 
 
12
 
 
13
  class DataError(Exception):
14
  """Custom exception for data-related errors"""
15
+ def __init__(self, message: str, error_code: str = None, details: Dict = None):
16
+ self.message = message
17
+ self.error_code = error_code or "DATA_ERROR"
18
+ self.details = details or {}
19
+ super().__init__(self.message)
20
+
21
 
22
  class ValidationError(Exception):
23
  """Custom exception for validation errors"""
24
+ def __init__(self, message: str, field: str = None, value: Any = None):
25
+ self.message = message
26
+ self.field = field
27
+ self.value = value
28
+ super().__init__(self.message)
29
 
 
 
 
 
 
30
 
31
+ class ProcessingError(Exception):
32
+ """Custom exception for processing errors"""
33
+ def __init__(self, message: str, step: str = None, context: Dict = None):
34
+ self.message = message
35
+ self.step = step
36
+ self.context = context or {}
37
+ super().__init__(self.message)
38
+
39
 
40
  def handle_data_exceptions(func: Callable) -> Callable:
41
  """
42
+ Decorator to handle data-related exceptions
43
+
44
+ Args:
45
+ func: Function to wrap with exception handling
46
+
47
+ Returns:
48
+ Wrapped function with exception handling
49
  """
50
  @functools.wraps(func)
51
  def wrapper(*args, **kwargs):
52
  try:
53
  return func(*args, **kwargs)
54
+ except DataError as e:
55
+ log_error(f"Data error in {func.__name__}: {e.message}",
56
+ error=e,
57
+ extra_data={"error_code": e.error_code, "details": e.details})
58
+ raise
59
+ except ValidationError as e:
60
+ log_error(f"Validation error in {func.__name__}: {e.message}",
61
+ error=e,
62
+ extra_data={"field": e.field, "value": e.value})
63
+ raise
64
+ except ProcessingError as e:
65
+ log_error(f"Processing error in {func.__name__}: {e.message}",
66
+ error=e,
67
+ extra_data={"step": e.step, "context": e.context})
68
+ raise
69
  except Exception as e:
70
+ log_error(f"Unexpected error in {func.__name__}: {str(e)}",
71
+ error=e)
72
+ raise ProcessingError(f"Unexpected error in {func.__name__}",
73
+ context={"original_error": str(e)})
74
+
75
  return wrapper
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ def safe_execute(func: Callable, *args, default_return=None, **kwargs) -> tuple:
79
  """
80
+ Safely execute a function and return success status with result
81
+
82
+ Args:
83
+ func: Function to execute
84
+ *args: Positional arguments for the function
85
+ default_return: Default value to return on error
86
+ **kwargs: Keyword arguments for the function
87
+
88
+ Returns:
89
+ tuple: (success: bool, result: Any, error: Optional[Exception])
90
  """
91
+ try:
92
+ result = func(*args, **kwargs)
93
+ return True, result, None
94
+ except Exception as e:
95
+ log_error(f"Error executing {func.__name__}: {str(e)}", error=e)
96
+ return False, default_return, e
 
 
 
97
 
98
+
99
+ def validate_data(data: Any, validation_rules: Dict) -> bool:
100
  """
101
+ Validate data against a set of rules
102
+
103
+ Args:
104
+ data: Data to validate
105
+ validation_rules: Dictionary of validation rules
106
+
107
+ Returns:
108
+ bool: True if validation passes
109
+
110
+ Raises:
111
+ ValidationError: If validation fails
112
  """
113
+ if not isinstance(validation_rules, dict):
114
+ raise ValidationError("Validation rules must be a dictionary")
115
+
116
+ for field, rules in validation_rules.items():
117
+ if isinstance(data, dict):
118
+ value = data.get(field)
119
+ else:
120
+ value = getattr(data, field, None)
121
+
122
+ # Check required fields
123
+ if rules.get('required', False) and value is None:
124
+ raise ValidationError(f"Field '{field}' is required", field=field, value=value)
125
+
126
+ # Check data type
127
+ if 'type' in rules and value is not None:
128
+ expected_type = rules['type']
129
+ if not isinstance(value, expected_type):
130
+ raise ValidationError(
131
+ f"Field '{field}' must be of type {expected_type.__name__}, got {type(value).__name__}",
132
+ field=field,
133
+ value=value
134
+ )
135
+
136
+ # Check value range for numbers
137
+ if 'min_value' in rules and isinstance(value, (int, float)):
138
+ if value < rules['min_value']:
139
+ raise ValidationError(
140
+ f"Field '{field}' must be >= {rules['min_value']}, got {value}",
141
+ field=field,
142
+ value=value
143
+ )
144
+
145
+ if 'max_value' in rules and isinstance(value, (int, float)):
146
+ if value > rules['max_value']:
147
+ raise ValidationError(
148
+ f"Field '{field}' must be <= {rules['max_value']}, got {value}",
149
+ field=field,
150
+ value=value
151
+ )
152
+
153
+ # Check string length
154
+ if 'min_length' in rules and isinstance(value, str):
155
+ if len(value) < rules['min_length']:
156
+ raise ValidationError(
157
+ f"Field '{field}' must be at least {rules['min_length']} characters",
158
+ field=field,
159
+ value=value
160
+ )
161
+
162
+ if 'max_length' in rules and isinstance(value, str):
163
+ if len(value) > rules['max_length']:
164
+ raise ValidationError(
165
+ f"Field '{field}' must be at most {rules['max_length']} characters",
166
+ field=field,
167
+ value=value
168
+ )
169
+
170
  return True
171
 
 
 
 
 
 
 
 
 
 
172
 
173
+ def format_error_response(error: Exception) -> Dict:
174
  """
175
+ Format an error into a standardized response
176
+
177
+ Args:
178
+ error: Exception to format
179
+
180
+ Returns:
181
+ Dict: Formatted error response
182
  """
183
+ if isinstance(error, (DataError, ValidationError, ProcessingError)):
184
+ return {
185
+ "success": False,
186
+ "error_type": error.__class__.__name__,
187
+ "message": error.message,
188
+ "details": getattr(error, 'details', {}),
189
+ "field": getattr(error, 'field', None),
190
+ "error_code": getattr(error, 'error_code', None)
191
+ }
192
+ else:
193
+ return {
194
+ "success": False,
195
+ "error_type": "UnexpectedError",
196
+ "message": str(error),
197
+ "details": {}
198
+ }
199
 
 
 
 
 
 
 
 
 
 
200
 
201
+ def retry_on_failure(max_retries: int = 3, delay: float = 1.0, exceptions: tuple = (Exception,)):
202
  """
203
+ Decorator to retry function execution on failure
204
+
205
+ Args:
206
+ max_retries: Maximum number of retry attempts
207
+ delay: Delay between retries in seconds
208
+ exceptions: Tuple of exceptions to catch and retry on
209
+
210
+ Returns:
211
+ Decorator function
212
  """
213
+ def decorator(func: Callable) -> Callable:
214
+ @functools.wraps(func)
215
+ def wrapper(*args, **kwargs):
216
+ import time
217
+
218
+ last_exception = None
219
+ for attempt in range(max_retries + 1):
220
+ try:
221
+ return func(*args, **kwargs)
222
+ except exceptions as e:
223
+ last_exception = e
224
+ if attempt < max_retries:
225
+ log_warning(f"Attempt {attempt + 1} failed for {func.__name__}: {str(e)}. Retrying in {delay}s...")
226
+ time.sleep(delay)
227
+ else:
228
+ log_error(f"All {max_retries + 1} attempts failed for {func.__name__}", error=e)
229
+
230
+ raise last_exception
231
+
232
+ return wrapper
233
+ return decorator
234
+
235
+
236
+ # Context manager for error handling
237
+ class ErrorHandler:
238
+ """Context manager for comprehensive error handling"""
239
+
240
+ def __init__(self, operation_name: str, raise_on_error: bool = True):
241
+ self.operation_name = operation_name
242
+ self.raise_on_error = raise_on_error
243
+ self.error = None
244
+
245
+ def __enter__(self):
246
+ return self
247
+
248
+ def __exit__(self, exc_type, exc_val, exc_tb):
249
+ if exc_type is not None:
250
+ self.error = exc_val
251
+ log_error(f"Error in {self.operation_name}: {str(exc_val)}", error=exc_val)
252
+
253
+ if not self.raise_on_error:
254
+ return True # Suppress the exception
255
+
256
+ return False # Let the exception propagate