Files
mostovik-backend/src/apps/core/exceptions.py
Aleksandr Meshchriakov f121445313 feat(core): add core module with mixins, services, and background jobs
- Add Model Mixins: TimestampMixin, SoftDeleteMixin, AuditMixin, etc.
- Add Base Services: BaseService, BulkOperationsMixin, QueryOptimizerMixin
- Add Base ViewSets with bulk operations
- Add BackgroundJob model for Celery task tracking
- Add BaseAppCommand for management commands
- Add permissions, pagination, filters, cache, logging
- Migrate tests to factory_boy + faker
- Add CHANGELOG.md
- 297 tests passing
2026-01-21 11:47:26 +01:00

170 lines
4.2 KiB
Python

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