""" Core exceptions for the application. Provides a hierarchy of business logic exceptions that are automatically converted to appropriate API responses by the exception handler. """ from typing import Any class BaseAPIException(Exception): """ Base exception for all API-related errors. Attributes: message: Human-readable error message code: Machine-readable error code (e.g., 'validation_error') status_code: HTTP status code details: Additional error details (optional) """ message: str = "An error occurred" code: str = "error" status_code: int = 400 details: dict[str, Any] | None = None def __init__( self, message: str | None = None, code: str | None = None, details: dict[str, Any] | None = None, ): self.message = message or self.message self.code = code or self.code self.details = details super().__init__(self.message) def to_dict(self) -> dict[str, Any]: """Convert exception to dictionary for API response.""" result = { "code": self.code, "message": self.message, } if self.details: result["details"] = self.details return result # ============================================================================ # Client Errors (4xx) # ============================================================================ class ValidationError(BaseAPIException): """Raised when input data fails validation.""" message = "Validation error" code = "validation_error" status_code = 400 class BadRequestError(BaseAPIException): """Raised when request is malformed or invalid.""" message = "Bad request" code = "bad_request" status_code = 400 class AuthenticationError(BaseAPIException): """Raised when authentication fails.""" message = "Authentication failed" code = "authentication_error" status_code = 401 class PermissionDeniedError(BaseAPIException): """Raised when user lacks required permissions.""" message = "Permission denied" code = "permission_denied" status_code = 403 class NotFoundError(BaseAPIException): """Raised when requested resource is not found.""" message = "Resource not found" code = "not_found" status_code = 404 class ConflictError(BaseAPIException): """Raised when action conflicts with current state.""" message = "Conflict with current state" code = "conflict" status_code = 409 class RateLimitError(BaseAPIException): """Raised when rate limit is exceeded.""" message = "Rate limit exceeded" code = "rate_limit_exceeded" status_code = 429 # ============================================================================ # Server Errors (5xx) # ============================================================================ class InternalError(BaseAPIException): """Raised for unexpected internal errors.""" message = "Internal server error" code = "internal_error" status_code = 500 class ServiceUnavailableError(BaseAPIException): """Raised when a dependent service is unavailable.""" message = "Service temporarily unavailable" code = "service_unavailable" status_code = 503 # ============================================================================ # Business Logic Errors # ============================================================================ class BusinessLogicError(BaseAPIException): """ Base class for business logic errors. Use this for domain-specific errors that should return 400/422. """ message = "Business logic error" code = "business_error" status_code = 400 class InvalidStateError(BusinessLogicError): """Raised when entity is in invalid state for requested operation.""" message = "Invalid state for this operation" code = "invalid_state" status_code = 400 class DuplicateError(BusinessLogicError): """Raised when attempting to create a duplicate resource.""" message = "Resource already exists" code = "duplicate" status_code = 409 class QuotaExceededError(BusinessLogicError): """Raised when a resource quota is exceeded.""" message = "Quota exceeded" code = "quota_exceeded" status_code = 400