|
|
""" |
|
|
Custom exceptions and error handling for Silver Table Assistant. |
|
|
Provides structured error handling and logging. |
|
|
""" |
|
|
|
|
|
import logging |
|
|
from typing import Any, Dict, Optional |
|
|
from fastapi import HTTPException, status |
|
|
from sqlalchemy.exc import SQLAlchemyError, IntegrityError |
|
|
try: |
|
|
from stripe import StripeError, CardError, AuthenticationError, InvalidRequestError |
|
|
except ImportError: |
|
|
|
|
|
class StripeError(Exception): pass |
|
|
class CardError(StripeError): pass |
|
|
class AuthenticationError(StripeError): pass |
|
|
class InvalidRequestError(StripeError): pass |
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class SilverTableException(Exception): |
|
|
"""Base exception for Silver Table Assistant.""" |
|
|
|
|
|
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None): |
|
|
self.message = message |
|
|
self.details = details or {} |
|
|
super().__init__(self.message) |
|
|
|
|
|
def to_dict(self) -> Dict[str, Any]: |
|
|
"""Convert exception to dictionary for logging/response.""" |
|
|
return { |
|
|
"error": self.__class__.__name__, |
|
|
"message": self.message, |
|
|
"details": self.details |
|
|
} |
|
|
|
|
|
|
|
|
class DatabaseException(SilverTableException): |
|
|
"""Database-related errors.""" |
|
|
pass |
|
|
|
|
|
|
|
|
class PaymentException(SilverTableException): |
|
|
"""Payment/Stripe-related errors.""" |
|
|
pass |
|
|
|
|
|
|
|
|
class AuthenticationException(SilverTableException): |
|
|
"""Authentication-related errors.""" |
|
|
pass |
|
|
|
|
|
|
|
|
class ValidationException(SilverTableException): |
|
|
"""Data validation errors.""" |
|
|
pass |
|
|
|
|
|
|
|
|
class ExternalServiceException(SilverTableException): |
|
|
"""External service (OpenAI, etc.) errors.""" |
|
|
pass |
|
|
|
|
|
|
|
|
def handle_database_error(error: Exception, operation: str) -> DatabaseException: |
|
|
"""Handle database errors with appropriate logging.""" |
|
|
|
|
|
if isinstance(error, IntegrityError): |
|
|
message = f"Database integrity error during {operation}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "integrity_error"}) |
|
|
elif isinstance(error, SQLAlchemyError): |
|
|
message = f"Database operation failed during {operation}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "sqlalchemy_error"}) |
|
|
else: |
|
|
message = f"Unexpected database error during {operation}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "unknown_db_error"}) |
|
|
|
|
|
return DatabaseException(message, {"operation": operation, "original_error": str(error)}) |
|
|
|
|
|
|
|
|
def handle_payment_error(error: Exception, operation: str) -> PaymentException: |
|
|
"""Handle Stripe payment errors with appropriate logging.""" |
|
|
|
|
|
if isinstance(error, CardError): |
|
|
message = f"Payment card error during {operation}: {error.user_message}" |
|
|
logger.warning(f"{message}: {str(error)}", extra={"error_type": "card_error"}) |
|
|
elif isinstance(error, AuthenticationError): |
|
|
message = f"Payment authentication error during {operation}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "auth_error"}) |
|
|
elif isinstance(error, InvalidRequestError): |
|
|
message = f"Invalid payment request during {operation}: {str(error)}" |
|
|
logger.warning(f"{message}: {str(error)}", extra={"error_type": "invalid_request"}) |
|
|
elif isinstance(error, StripeError): |
|
|
message = f"Stripe error during {operation}: {str(error)}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "stripe_error"}) |
|
|
else: |
|
|
message = f"Unexpected payment error during {operation}" |
|
|
logger.error(f"{message}: {str(error)}", extra={"error_type": "unknown_payment_error"}) |
|
|
|
|
|
return PaymentException(message, {"operation": operation, "original_error": str(error)}) |
|
|
|
|
|
|
|
|
def handle_external_service_error(error: Exception, service: str, operation: str) -> ExternalServiceException: |
|
|
"""Handle external service errors (OpenAI, etc.).""" |
|
|
|
|
|
message = f"{service} service error during {operation}: {str(error)}" |
|
|
logger.error(message, extra={"error_type": "external_service_error", "service": service}) |
|
|
|
|
|
return ExternalServiceException( |
|
|
message, |
|
|
{"service": service, "operation": operation, "original_error": str(error)} |
|
|
) |
|
|
|
|
|
|
|
|
def handle_authentication_error(error: Exception, operation: str) -> AuthenticationException: |
|
|
"""Handle authentication errors.""" |
|
|
|
|
|
message = f"Authentication error during {operation}: {str(error)}" |
|
|
logger.warning(message, extra={"error_type": "auth_error"}) |
|
|
|
|
|
return AuthenticationException(message, {"operation": operation, "original_error": str(error)}) |
|
|
|
|
|
|
|
|
def handle_validation_error(error: Exception, field: str, operation: str) -> ValidationException: |
|
|
"""Handle validation errors.""" |
|
|
|
|
|
message = f"Validation error in {field} during {operation}: {str(error)}" |
|
|
logger.warning(message, extra={"error_type": "validation_error", "field": field}) |
|
|
|
|
|
return ValidationException( |
|
|
message, |
|
|
{"field": field, "operation": operation, "original_error": str(error)} |
|
|
) |
|
|
|
|
|
|
|
|
def http_exception_from_custom(exception: SilverTableException, status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR) -> HTTPException: |
|
|
"""Convert custom exception to FastAPI HTTPException.""" |
|
|
|
|
|
return HTTPException( |
|
|
status_code=status_code, |
|
|
detail={ |
|
|
"error": exception.__class__.__name__, |
|
|
"message": exception.message, |
|
|
"details": exception.details |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
def log_and_handle_error( |
|
|
error: Exception, |
|
|
context: str, |
|
|
operation: str, |
|
|
reraise: bool = True |
|
|
) -> Optional[SilverTableException]: |
|
|
""" |
|
|
Centralized error handling with logging. |
|
|
|
|
|
Args: |
|
|
error: The exception that occurred |
|
|
context: Context where the error occurred |
|
|
operation: Operation being performed |
|
|
reraise: Whether to reraise as custom exception |
|
|
|
|
|
Returns: |
|
|
Custom exception if not reraising, None otherwise |
|
|
""" |
|
|
|
|
|
try: |
|
|
if isinstance(error, (SQLAlchemyError, IntegrityError)): |
|
|
custom_exception = handle_database_error(error, operation) |
|
|
elif isinstance(error, StripeError): |
|
|
custom_exception = handle_payment_error(error, operation) |
|
|
elif isinstance(error, (AuthenticationError,)): |
|
|
custom_exception = handle_authentication_error(error, operation) |
|
|
else: |
|
|
|
|
|
message = f"Unexpected error during {operation} in {context}: {str(error)}" |
|
|
logger.error(message, extra={"error_type": "unexpected_error", "context": context}) |
|
|
custom_exception = SilverTableException( |
|
|
message, |
|
|
{"context": context, "operation": operation, "original_error": str(error)} |
|
|
) |
|
|
|
|
|
if reraise: |
|
|
raise custom_exception |
|
|
else: |
|
|
return custom_exception |
|
|
|
|
|
except Exception as handling_error: |
|
|
logger.error(f"Error in error handler: {str(handling_error)}") |
|
|
if reraise: |
|
|
raise SilverTableException(f"Error handling failed: {str(handling_error)}") |
|
|
else: |
|
|
return SilverTableException(f"Error handling failed: {str(handling_error)}") |
|
|
|
|
|
|
|
|
|
|
|
class DatabaseTransaction: |
|
|
"""Context manager for database transactions with proper error handling.""" |
|
|
|
|
|
def __init__(self, session): |
|
|
self.session = session |
|
|
|
|
|
async def __aenter__(self): |
|
|
return self.session |
|
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb): |
|
|
if exc_type is not None: |
|
|
|
|
|
await self.session.rollback() |
|
|
logger.error(f"Database transaction rolled back due to: {exc_val}") |
|
|
else: |
|
|
|
|
|
try: |
|
|
await self.session.commit() |
|
|
except Exception as e: |
|
|
await self.session.rollback() |
|
|
raise handle_database_error(e, "transaction_commit") |
|
|
|
|
|
await self.session.close() |
|
|
|
|
|
|
|
|
|
|
|
def handle_errors(operation: str, reraise: bool = True): |
|
|
"""Decorator for automatic error handling in functions.""" |
|
|
|
|
|
def decorator(func): |
|
|
async def wrapper(*args, **kwargs): |
|
|
try: |
|
|
return await func(*args, **kwargs) |
|
|
except Exception as e: |
|
|
return log_and_handle_error(e, func.__name__, operation, reraise) |
|
|
|
|
|
return wrapper |
|
|
|
|
|
return decorator |