open-navigator / api /errors.py
jcbowyer's picture
Clean HuggingFace deployment without binary files
61d29fc
"""
Structured error models for API responses.
Provides user-friendly error messages with expandable technical details.
"""
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
class ErrorDetail(BaseModel):
"""Structured error detail with user-friendly message and technical info."""
message: str = Field(
...,
description="User-friendly error message"
)
error_type: str = Field(
...,
description="Type of error (e.g., 'data_not_found', 'network_error', 'validation_error')"
)
technical_details: Optional[str] = Field(
None,
description="Technical error details for debugging (expandable in UI)"
)
suggestions: Optional[list[str]] = Field(
None,
description="Helpful suggestions for the user"
)
metadata: Optional[Dict[str, Any]] = Field(
None,
description="Additional context (state, dataset name, etc.)"
)
def parse_error(exception: Exception, context: Optional[Dict[str, Any]] = None) -> ErrorDetail:
"""
Parse an exception into a structured ErrorDetail with user-friendly message.
Args:
exception: The exception to parse
context: Additional context (state, dataset, etc.)
Returns:
ErrorDetail with user-friendly message and technical details
"""
error_str = str(exception)
context = context or {}
# Parse HuggingFace dataset not found errors
if "HTTP 404 Not Found" in error_str and "huggingface.co/datasets" in error_str:
# Extract dataset name from URL
import re
match = re.search(r'datasets/([^/]+/[^/]+)/', error_str)
dataset_name = match.group(1) if match else "unknown"
# Extract state from dataset name or context
state = context.get('state', 'Unknown')
data_type = 'bills' if 'bills' in dataset_name else 'data'
return ErrorDetail(
message=f"No {data_type} data available for {state.upper()}",
error_type="data_not_found",
technical_details=f"Dataset '{dataset_name}' not found on HuggingFace.\n\nFull error: {error_str}",
suggestions=[
f"Try a different state - we have data for 50+ states",
f"Check /api/bills/map to see which states have {data_type} data",
"Contact support if you believe this data should be available"
],
metadata={
"dataset": dataset_name,
"state": state,
"data_type": data_type
}
)
# Parse file not found errors (local environment)
elif "No such file or directory" in error_str or "FileNotFoundError" in error_str:
state = context.get('state', 'Unknown')
data_type = context.get('data_type', 'data')
return ErrorDetail(
message=f"No {data_type} available for {state.upper()}",
error_type="data_not_found",
technical_details=error_str,
suggestions=[
f"This state may not have {data_type} in our database yet",
"Try a different state or check which states have data",
"Data is being continuously added - check back later"
],
metadata={
"state": state,
"data_type": data_type
}
)
# Parse DuckDB/SQL errors
elif "DuckDB" in error_str or "SYNTAX ERROR" in error_str or "LINE" in error_str:
return ErrorDetail(
message="Database query error - please check your search parameters",
error_type="query_error",
technical_details=error_str,
suggestions=[
"Try simplifying your search query",
"Check that all parameters are valid",
"Contact support if the issue persists"
],
metadata=context
)
# Parse network/timeout errors
elif "timeout" in error_str.lower() or "connection" in error_str.lower():
return ErrorDetail(
message="Network request timed out - please try again",
error_type="network_error",
technical_details=error_str,
suggestions=[
"Try again in a few seconds",
"Check your internet connection",
"The server may be temporarily busy"
],
metadata=context
)
# Parse validation errors
elif "validation" in error_str.lower() or "invalid" in error_str.lower():
return ErrorDetail(
message="Invalid request parameters",
error_type="validation_error",
technical_details=error_str,
suggestions=[
"Check that all required parameters are provided",
"Verify parameter formats (e.g., state codes should be 2 letters)",
"See API documentation for valid parameter values"
],
metadata=context
)
# Generic error fallback
else:
return ErrorDetail(
message="An unexpected error occurred",
error_type="server_error",
technical_details=error_str,
suggestions=[
"Try again in a few moments",
"Contact support if the issue persists",
"Check the technical details for more information"
],
metadata=context
)