- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter) - Add comprehensive pyproject.toml configuration - Simplify Makefile code quality targets - Configure exclusions for venv/.venv in pyproject.toml - Auto-fix 1,359 linting issues across codebase Benefits: - Much faster builds (Ruff is written in Rust) - Single tool replaces multiple tools - More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q) - All configuration centralized in pyproject.toml - Better import sorting and formatting consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
213 lines
5.6 KiB
Python
213 lines
5.6 KiB
Python
# app/exceptions/base.py
|
|
"""
|
|
Base exception classes for the application.
|
|
|
|
This module provides classes and functions for:
|
|
- Base exception class with consistent error formatting
|
|
- Common exception types for different error categories
|
|
- Standardized error response structure
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
|
|
class WizamartException(Exception):
|
|
"""Base exception class for all custom exceptions."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
error_code: str,
|
|
status_code: int = 400,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
self.message = message
|
|
self.error_code = error_code
|
|
self.status_code = status_code
|
|
self.details = details or {}
|
|
super().__init__(self.message)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert exception to dictionary for JSON response."""
|
|
result = {
|
|
"error_code": self.error_code,
|
|
"message": self.message,
|
|
"status_code": self.status_code,
|
|
}
|
|
if self.details:
|
|
result["details"] = self.details
|
|
return result
|
|
|
|
|
|
class ValidationException(WizamartException):
|
|
"""Raised when request validation fails."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
field: str | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
validation_details = details or {}
|
|
if field:
|
|
validation_details["field"] = field
|
|
|
|
super().__init__(
|
|
message=message,
|
|
error_code="VALIDATION_ERROR",
|
|
status_code=422,
|
|
details=validation_details,
|
|
)
|
|
|
|
|
|
class AuthenticationException(WizamartException):
|
|
"""Raised when authentication fails."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Authentication failed",
|
|
error_code: str = "AUTHENTICATION_FAILED",
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=401,
|
|
details=details,
|
|
)
|
|
|
|
|
|
class AuthorizationException(WizamartException):
|
|
"""Raised when user lacks permission for an operation."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Access denied",
|
|
error_code: str = "AUTHORIZATION_FAILED",
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=403,
|
|
details=details,
|
|
)
|
|
|
|
|
|
class ResourceNotFoundException(WizamartException):
|
|
"""Raised when a requested resource is not found."""
|
|
|
|
def __init__(
|
|
self,
|
|
resource_type: str,
|
|
identifier: str,
|
|
message: str | None = None,
|
|
error_code: str | None = None,
|
|
):
|
|
if not message:
|
|
message = f"{resource_type} with identifier '{identifier}' not found"
|
|
if not error_code:
|
|
error_code = f"{resource_type.upper()}_NOT_FOUND"
|
|
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=404,
|
|
details={
|
|
"resource_type": resource_type,
|
|
"identifier": identifier,
|
|
},
|
|
)
|
|
|
|
|
|
class ConflictException(WizamartException):
|
|
"""Raised when a resource conflict occurs."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
error_code: str = "RESOURCE_CONFLICT",
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=409,
|
|
details=details,
|
|
)
|
|
|
|
|
|
class BusinessLogicException(WizamartException):
|
|
"""Raised when business logic rules are violated."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
error_code: str,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=400,
|
|
details=details,
|
|
)
|
|
|
|
|
|
class ExternalServiceException(WizamartException):
|
|
"""Raised when an external service fails."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
service_name: str,
|
|
error_code: str = "EXTERNAL_SERVICE_ERROR",
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
service_details = details or {}
|
|
service_details["service_name"] = service_name
|
|
|
|
super().__init__(
|
|
message=message,
|
|
error_code=error_code,
|
|
status_code=502,
|
|
details=service_details,
|
|
)
|
|
|
|
|
|
class RateLimitException(WizamartException):
|
|
"""Raised when rate limit is exceeded."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Rate limit exceeded",
|
|
retry_after: int | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
):
|
|
rate_limit_details = details or {}
|
|
if retry_after:
|
|
rate_limit_details["retry_after"] = retry_after
|
|
|
|
super().__init__(
|
|
message=message,
|
|
error_code="RATE_LIMIT_EXCEEDED",
|
|
status_code=429,
|
|
details=rate_limit_details,
|
|
)
|
|
|
|
|
|
class ServiceUnavailableException(WizamartException):
|
|
"""Raised when service is unavailable."""
|
|
|
|
def __init__(self, message: str = "Service temporarily unavailable"):
|
|
super().__init__(
|
|
message=message,
|
|
error_code="SERVICE_UNAVAILABLE",
|
|
status_code=503,
|
|
)
|
|
|
|
|
|
# Note: Domain-specific exceptions like VendorNotFoundException, UserNotFoundException, etc.
|
|
# are defined in their respective domain modules (vendor.py, admin.py, etc.)
|
|
# to keep domain-specific logic separate from base exceptions.
|