| | """ |
| | 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 |