Skip to content

Exception Handling

ZodiacCore provides a centralized exception handling system that automatically converts Python exceptions into standardized, production-ready JSON responses.

1. Core Concepts

The ZodiacException Base

All business logic errors should inherit from ZodiacException. This base class allows you to define:

  • http_code: The HTTP status code (e.g., 404, 400).
  • code: A custom business error code.
  • message: A human-readable error description.
  • data: Optional payload for additional error details (e.g., validation errors).

Automatic Transformation

When a ZodiacException is raised, the handler_zodiac_exception exception handler catches it and transforms it into a standard JSON response:

{
  "code": 404,
  "message": "Resource not found",
  "data": null
}

2. Validation Errors (HTTP 422)

One of the best features of ZodiacCore is that it also standardizes framework-level validation errors. When a user sends invalid JSON or missing parameters, FastAPI normally returns a custom structure. ZodiacCore catches these and wraps them in our standard format:

{
  "code": 422,
  "message": "Unprocessable Entity",
  "data": [
    {
      "type": "missing",
      "loc": ["body", "username"],
      "msg": "Field required",
      "input": null
    }
  ]
}

This ensures your API is 100% consistent, whether the error came from your business logic or from a schema mismatch.


3. Built-in Exceptions

ZodiacCore includes several common exceptions ready to use:

Exception HTTP Status Use Case
BadRequestException 400 Invalid input or parameters.
UnauthorizedException 401 Missing or invalid authentication.
ForbiddenException 403 Insufficient permissions.
NotFoundException 404 Resource does not exist.
ConflictException 409 Resource state conflict (e.g., duplicate entry).
UnprocessableEntityException 422 Business/semantic validation failed (entity well-formed but not processable).

4. Custom Exceptions

Creating your own business exception is simple:

from zodiac_core.exceptions import ZodiacException
from fastapi import status

class InsufficientBalanceException(ZodiacException):
    # Set default HTTP code
    http_code = status.HTTP_400_BAD_REQUEST

    def __init__(self, current_balance: float):
        super().__init__(
            code=1001, # Custom business code
            message="Your account balance is too low.",
            data={"current_balance": current_balance}
        )

Usage in a route:

@app.post("/transfer")
async def transfer_money(amount: float):
    if amount > user.balance:
        raise InsufficientBalanceException(user.balance)
    ...


5. Integration

To enable global exception handling in your FastAPI app, use register_exception_handlers. This will catch:

  1. All ZodiacException subclasses.
  2. Pydantic ValidationError and FastAPI RequestValidationError (mapped to 422).
  3. Any uncaught Exception (mapped to 500 with secure logging).
from fastapi import FastAPI
from zodiac_core.exception_handlers import register_exception_handlers

app = FastAPI()
register_exception_handlers(app)

6. API Reference

Exception Base & Subclasses

ZodiacException

Bases: Exception

Base class for all zodiac-core related errors.

Source code in zodiac_core/exceptions.py
class ZodiacException(Exception):
    """Base class for all zodiac-core related errors."""

    http_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR

    def __init__(
        self,
        code: Optional[int] = None,
        data: Any = None,
        message: Optional[str] = None,
    ):
        self.code = code or self.http_code
        self.data = data
        if message is not None:
            self.message = message
        super().__init__(message)

BadRequestException

Bases: ZodiacException

Exception raised for 400 Bad Request errors.

Source code in zodiac_core/exceptions.py
class BadRequestException(ZodiacException):
    """Exception raised for 400 Bad Request errors."""

    http_code = status.HTTP_400_BAD_REQUEST

UnauthorizedException

Bases: ZodiacException

Exception raised for 401 Unauthorized errors.

Source code in zodiac_core/exceptions.py
class UnauthorizedException(ZodiacException):
    """Exception raised for 401 Unauthorized errors."""

    http_code = status.HTTP_401_UNAUTHORIZED

ForbiddenException

Bases: ZodiacException

Exception raised for 403 Forbidden errors.

Source code in zodiac_core/exceptions.py
class ForbiddenException(ZodiacException):
    """Exception raised for 403 Forbidden errors."""

    http_code = status.HTTP_403_FORBIDDEN

NotFoundException

Bases: ZodiacException

Exception raised for 404 Not Found errors.

Source code in zodiac_core/exceptions.py
class NotFoundException(ZodiacException):
    """Exception raised for 404 Not Found errors."""

    http_code = status.HTTP_404_NOT_FOUND

ConflictException

Bases: ZodiacException

Exception raised for 409 Conflict errors.

Source code in zodiac_core/exceptions.py
class ConflictException(ZodiacException):
    """Exception raised for 409 Conflict errors."""

    http_code = status.HTTP_409_CONFLICT

UnprocessableEntityException

Bases: ZodiacException

Exception raised for 422 Unprocessable Entity (business validation / semantic errors).

Source code in zodiac_core/exceptions.py
class UnprocessableEntityException(ZodiacException):
    """Exception raised for 422 Unprocessable Entity (business validation / semantic errors)."""

    http_code = status.HTTP_422_UNPROCESSABLE_CONTENT

Global Handler Registration

register_exception_handlers(app)

Register all exception handlers to the FastAPI app.

Order matters: 1. Specific Validation Errors 2. Custom Business Logic Errors (ZodiacException) 3. Global Catch-All (Exception)

Source code in zodiac_core/exception_handlers.py
def register_exception_handlers(app: FastAPI) -> None:
    """
    Register all exception handlers to the FastAPI app.

    Order matters:
    1. Specific Validation Errors
    2. Custom Business Logic Errors (ZodiacException)
    3. Global Catch-All (Exception)
    """
    app.add_exception_handler(RequestValidationError, handler_validation_exception)
    app.add_exception_handler(ValidationError, handler_validation_exception)
    app.add_exception_handler(ZodiacException, handler_zodiac_exception)
    app.add_exception_handler(Exception, handler_global_exception)