"""
Error handling utilities for vitalDSP webapp.
Provides consistent error handling and user feedback.
"""
import logging
import traceback
from typing import Dict, Any, Optional, Union
from dash import html
import dash_bootstrap_components as dbc
from ..config.settings import ui_styles
logger = logging.getLogger(__name__)
[docs]
class WebappError(Exception):
"""Base exception class for webapp errors."""
def __init__(
self, message: str, error_code: str = None, details: Dict[str, Any] = None
):
super().__init__(message)
self.message = message
self.error_code = error_code
self.details = details or {}
# Add timestamp
from datetime import datetime
self.timestamp = datetime.now()
# Add context attribute for backward compatibility
self.context = details or {}
# Add error_type attribute for backward compatibility
self.error_type = error_code
def __str__(self):
if self.error_code:
return f"[{self.error_code}] {self.message}"
return self.message
[docs]
class DataProcessingError(WebappError):
"""Exception raised for data processing errors."""
pass
[docs]
class FileUploadError(WebappError):
"""Exception raised for file upload errors."""
pass
[docs]
class ValidationError(WebappError):
"""Exception raised for validation errors."""
pass
[docs]
class AnalysisError(WebappError):
"""Exception raised for analysis errors."""
pass
[docs]
def handle_error(error: Exception, context: str = "Unknown") -> Dict[str, Any]:
"""
Handle an error and return standardized error information.
Parameters
----------
error : Exception
The error that occurred
context : str
Context where the error occurred
Returns
-------
Dict[str, Any]
Standardized error information
"""
error_info = {
"type": type(error).__name__,
"message": str(error),
"context": context,
"timestamp": None,
"traceback": None,
}
# Add timestamp
from datetime import datetime
error_info["timestamp"] = datetime.now().isoformat()
# Add traceback for logging
if logger.isEnabledFor(logging.DEBUG):
error_info["traceback"] = traceback.format_exc()
# Log the error
logger.error(f"Error in {context}: {error}", exc_info=True)
return error_info
[docs]
def create_error_alert(
error: Union[Exception, str],
title: str = "Error",
color: str = "danger",
show_details: bool = False,
dismissible: bool = False,
) -> dbc.Alert:
"""
Create a standardized error alert component.
Parameters
----------
error : Union[Exception, str]
Error object or error message string
title : str
Title for the error alert
color : str
Bootstrap color for the alert
show_details : bool
Whether to show error details
Returns
-------
dbc.Alert
Error alert component
"""
if isinstance(error, Exception):
message = str(error)
error_type = type(error).__name__
else:
message = error
error_type = "Error"
alert_content = [html.H5(f"❌ {title}", className="alert-heading"), html.P(message)]
if show_details and isinstance(error, Exception):
alert_content.extend(
[
html.Hr(),
html.P([html.Strong("Error Type: "), error_type], className="mb-1"),
html.P(
[html.Strong("Context: "), getattr(error, "context", "Unknown")],
className="mb-1",
),
]
)
return dbc.Alert(
alert_content,
color=color,
className="mt-3",
dismissable=dismissible, # DBC uses 'dismissable' not 'dismissible'
)
[docs]
def handle_upload_error(error: Exception, context: dict = None) -> dict:
"""
Handle file upload errors specifically.
Parameters
----------
error : Exception
The upload error that occurred
context : dict, optional
Additional context about the upload
Returns
-------
dict
Standardized error response for upload errors
"""
error_info = handle_error(error, "File Upload")
# Add upload-specific error handling
if isinstance(error, FileUploadError):
error_info["upload_context"] = context or {}
error_info["suggestions"] = get_upload_error_suggestions(error)
# Add the expected keys for the test
error_info["error"] = True
error_info["message"] = str(error)
return error_info
[docs]
def handle_processing_error(error: Exception, context: dict = None) -> dict:
"""
Handle data processing errors specifically.
Parameters
----------
error : Exception
The processing error that occurred
context : dict, optional
Additional context about the processing
Returns
-------
dict
Standardized error response for processing errors
"""
error_info = handle_error(error, "Data Processing")
# Add processing-specific error handling
if isinstance(error, DataProcessingError):
error_info["processing_context"] = context or {}
error_info["suggestions"] = get_processing_error_suggestions(error)
# Add the expected keys for the test
error_info["error"] = True
error_info["message"] = str(error)
return error_info
[docs]
def get_processing_error_suggestions(error: Exception) -> list:
"""Get user-friendly suggestions for processing errors."""
suggestions = []
if "data format" in str(error).lower():
suggestions.append("Check if the data format is supported")
suggestions.append("Ensure the data has the expected structure")
elif "column mapping" in str(error).lower():
suggestions.append("Verify the column mapping configuration")
suggestions.append("Check if all required columns are present")
elif "sampling frequency" in str(error).lower():
suggestions.append("Verify the sampling frequency value")
suggestions.append("Ensure the sampling frequency is positive")
else:
suggestions.append("Check the data for any anomalies")
suggestions.append("Verify the processing parameters")
return suggestions
[docs]
def handle_analysis_error(error: Exception, context: dict = None) -> dict:
"""
Handle analysis errors specifically.
Parameters
----------
error : Exception
The analysis error that occurred
context : dict, optional
Additional context about the analysis
Returns
-------
dict
Standardized error response for analysis errors
"""
error_info = handle_error(error, "Data Analysis")
# Add analysis-specific error handling
if isinstance(error, AnalysisError):
error_info["analysis_context"] = context or {}
error_info["suggestions"] = get_analysis_error_suggestions(error)
# Add the expected keys for the test
error_info["error"] = True
error_info["message"] = str(error)
return error_info
[docs]
def get_analysis_error_suggestions(error: Exception) -> list:
"""Get user-friendly suggestions for analysis errors."""
suggestions = []
if "insufficient data" in str(error).lower():
suggestions.append("Ensure you have enough data points for analysis")
suggestions.append("Check if the data window size is appropriate")
elif "parameter" in str(error).lower():
suggestions.append("Verify the analysis parameters")
suggestions.append("Check if parameter values are within valid ranges")
else:
suggestions.append("Review the analysis configuration")
suggestions.append("Check if the data quality is sufficient")
return suggestions
[docs]
def log_error_with_context(error: Exception, context: dict, level: str = "ERROR"):
"""
Log an error with additional context information.
Parameters
----------
error : Exception
The error to log
context : dict
Additional context information
level : str
Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
"""
context_str = " | ".join([f"{k}: {v}" for k, v in context.items()])
error_msg = f"{str(error)} | Context: {context_str}"
if level.upper() == "DEBUG":
logger.debug(error_msg)
elif level.upper() == "INFO":
logger.info(error_msg)
elif level.upper() == "WARNING":
logger.warning(error_msg)
elif level.upper() == "ERROR":
logger.error(error_msg)
elif level.upper() == "CRITICAL":
logger.critical(error_msg)
else:
logger.error(error_msg)
[docs]
def create_user_friendly_error_message(error: Exception) -> str:
"""
Create a user-friendly error message from an exception.
Parameters
----------
error : Exception
The error to convert to a user-friendly message
Returns
-------
str
User-friendly error message
"""
if isinstance(error, WebappError):
error_type = error.error_code or "Unknown Error"
if "upload" in error_type.lower():
return (
f"Upload Error: {error.message}. Please check your file and try again."
)
elif "processing" in error_type.lower():
return f"Processing Error: {error.message}. Please verify your data and settings."
elif "analysis" in error_type.lower():
return f"Analysis Error: {error.message}. Please check your analysis parameters."
else:
return f"{error_type}: {error.message}"
else:
# Generic error handling
error_msg = str(error)
if "file" in error_msg.lower():
return f"File Error: {error_msg}. Please check your file and try again."
elif "data" in error_msg.lower():
return f"Data Error: {error_msg}. Please verify your data and try again."
else:
return (
f"An error occurred: {error_msg}. Please try again or contact support."
)
[docs]
def get_upload_error_suggestions(error: Exception) -> list:
"""Get user-friendly suggestions for upload errors."""
suggestions = []
if "file size" in str(error).lower():
suggestions.append("Try uploading a smaller file")
suggestions.append("Check if the file is compressed")
elif "file format" in str(error).lower():
suggestions.append("Ensure the file is in CSV, Excel, or text format")
suggestions.append("Check if the file extension matches the content")
elif "permission" in str(error).lower():
suggestions.append("Check file permissions")
suggestions.append("Try running the application with appropriate privileges")
else:
suggestions.append("Check the file for corruption")
suggestions.append("Try uploading a different file")
return suggestions
[docs]
def create_warning_alert(message: str, title: str = "Warning") -> dbc.Alert:
"""
Create a standardized warning alert component.
Parameters
----------
message : str
Warning message
title : str
Title for the warning alert
Returns
-------
dbc.Alert
Warning alert component
"""
return dbc.Alert(
[html.H5(f"⚠️ {title}", className="alert-heading"), html.P(message)],
color="warning",
className="mt-3",
)
[docs]
def create_info_alert(message: str, title: str = "Information") -> dbc.Alert:
"""
Create a standardized info alert component.
Parameters
----------
message : str
Information message
title : str
Title for the info alert
Returns
-------
dbc.Alert
Info alert component
"""
return dbc.Alert(
[html.H5(f"ℹ️ {title}", className="alert-heading"), html.P(message)],
color="info",
className="mt-3",
)
[docs]
def create_success_alert(message: str, title: str = "Success") -> dbc.Alert:
"""
Create a standardized success alert component.
Parameters
----------
message : str
Success message
title : str
Title for the success alert
Returns
-------
dbc.Alert
Success alert component
"""
return dbc.Alert(
[html.H5(f"✅ {title}", className="alert-heading"), html.P(message)],
color="success",
className="mt-3",
)
[docs]
def safe_execute(
func, *args, default_return=None, error_context: str = "Unknown", **kwargs
):
"""
Safely execute a function with error handling.
Parameters
----------
func : callable
Function to execute
*args : tuple
Positional arguments for the function
default_return : Any
Default return value if function fails
error_context : str
Context for error logging
**kwargs : dict
Keyword arguments for the function
Returns
-------
Any
Function result or default_return if function fails
"""
try:
return func(*args, **kwargs)
except Exception as e:
handle_error(e, error_context)
return default_return
[docs]
def validate_required_fields(
data: Dict[str, Any], required_fields: list, context: str = "Data validation"
) -> None:
"""
Validate that required fields are present in data.
Parameters
----------
data : Dict[str, Any]
Data to validate
required_fields : list
List of required field names
context : str
Context for error messages
Raises
------
ValidationError
If required fields are missing
"""
missing_fields = [
field for field in required_fields if field not in data or data[field] is None
]
if missing_fields:
raise ValidationError(
f"Missing required fields: {', '.join(missing_fields)}",
error_code="MISSING_FIELDS",
details={"missing_fields": missing_fields, "context": context},
)
[docs]
def validate_data_types(
data: Dict[str, Any],
field_types: Dict[str, type],
context: str = "Data type validation",
) -> None:
"""
Validate data types of fields.
Parameters
----------
data : Dict[str, Any]
Data to validate
field_types : Dict[str, type]
Dictionary mapping field names to expected types
context : str
Context for error messages
Raises
------
ValidationError
If data types don't match expected types
"""
type_errors = []
for field, expected_type in field_types.items():
if field in data and data[field] is not None:
if not isinstance(data[field], expected_type):
type_errors.append(
f"{field}: expected {expected_type.__name__}, got {type(data[field]).__name__}"
)
if type_errors:
raise ValidationError(
f"Data type validation failed: {'; '.join(type_errors)}",
error_code="TYPE_MISMATCH",
details={"type_errors": type_errors, "context": context},
)