""" Custom exception hierarchy. All application exceptions inherit from AppException. """ from typing import Any, Optional class AppException(Exception): """Base exception for application errors.""" def __init__( self, message: str, status_code: int = 500, error_code: Optional[str] = None, details: Optional[dict[str, Any]] = None, ): self.message = message self.status_code = status_code self.error_code = error_code or self.__class__.__name__ self.details = details or {} super().__init__(message) def to_dict(self) -> dict[str, Any]: """Convert to JSON-serializable dict.""" return { "error": self.error_code, "message": self.message, "details": self.details, } class NotFoundError(AppException): """Resource not found.""" def __init__(self, resource: str, identifier: Any): super().__init__( message=f"{resource} not found: {identifier}", status_code=404, details={"resource": resource, "identifier": str(identifier)}, ) class ValidationError(AppException): """Input validation failed.""" def __init__(self, message: str, field: Optional[str] = None): super().__init__( message=message, status_code=422, details={"field": field} if field else {}, ) class AuthenticationError(AppException): """Authentication required or failed.""" def __init__(self, message: str = "Authentication required"): super().__init__(message=message, status_code=401) class AuthorizationError(AppException): """Permission denied.""" def __init__(self, message: str = "Permission denied"): super().__init__(message=message, status_code=403) class ConflictError(AppException): """Resource conflict (e.g., duplicate).""" def __init__(self, message: str): super().__init__(message=message, status_code=409) class RateLimitError(AppException): """Rate limit exceeded.""" def __init__(self, retry_after: int = 60): super().__init__( message="Rate limit exceeded", status_code=429, details={"retry_after": retry_after}, ) class ServiceUnavailableError(AppException): """External service unavailable.""" def __init__(self, service: str, message: Optional[str] = None): super().__init__( message=message or f"Service unavailable: {service}", status_code=503, details={"service": service}, ) class TimeoutError(AppException): """Operation timed out.""" def __init__(self, operation: str, timeout_seconds: float): super().__init__( message=f"Operation timed out: {operation}", status_code=504, details={"operation": operation, "timeout_seconds": timeout_seconds}, )