How Open Notebook Maps Its Custom Exception Hierarchy to HTTP Status Codes

Open Notebook converts domain-specific Python exceptions into standardized HTTP responses by registering FastAPI exception handlers in api/main.py that map each exception type to a specific status code, ranging from 400 for invalid input to 502 for external service failures.

The lfnovo/open-notebook project implements a robust error handling strategy that bridges Python's exception mechanism with HTTP semantics. In open_notebook/exceptions.py, the codebase defines a custom exception hierarchy, while api/main.py registers FastAPI handlers that map these exceptions to HTTP status codes with appropriate CORS headers.

The Domain-Specific Exception Hierarchy

The open_notebook/exceptions.py module defines a base OpenNotebookError class that serves as the parent for all application-specific exceptions. This hierarchy includes specialized exceptions for different failure modes, from missing resources to external service outages.

Key exceptions include:

  • NotFoundError: Raised when a requested resource (e.g., notebook or source) does not exist.
  • InvalidInputError: Signals malformed or invalid client data.
  • AuthenticationError: Indicates authentication failures or invalid credentials.
  • RateLimitError: Triggered when clients exceed request quotas.
  • ConfigurationError: Detects misconfiguration issues, such as missing environment variables.
  • NetworkError: Captures underlying network failures when calling external services.
  • ExternalServiceError: Raised when third-party AI services return errors.
  • DatabaseOperationError, FileOperationError, UnsupportedTypeException, and NoTranscriptFound: Additional domain-specific errors that inherit from the base class.

Mapping Exceptions to HTTP Status Codes

In api/main.py, FastAPI exception handlers convert raised exceptions into HTTP responses using the @app.exception_handler decorator. The mapping follows REST conventions, using 4xx codes for client errors and 5xx codes for server failures.

Client Error Responses (4xx)

The following mappings handle client-side issues:

  • 404 Not Found: NotFoundError handler at lines 17‑23.
  • 400 Bad Request: InvalidInputError handler at lines 26‑32.
  • 401 Unauthorized: AuthenticationError handler at lines 35‑41.
  • 429 Too Many Requests: RateLimitError handler at lines 44‑50.
  • 422 Unprocessable Entity: ConfigurationError handler at lines 53‑59.

Server Error Responses (5xx)

Server and external service failures map to 5xx codes:

  • 502 Bad Gateway: Both NetworkError (lines 62‑68) and ExternalServiceError (lines 71‑77) return this status when external calls fail.
  • 500 Internal Server Error: The base OpenNotebookError handler (lines 80‑86) catches any uncaught application exceptions. DatabaseOperationError, UnsupportedTypeException, FileOperationError, and NoTranscriptFound fall back to this handler because they inherit from OpenNotebookError but lack specific handlers.

How Exception Resolution Works

When an exception is raised inside a service or router, FastAPI traverses the exception hierarchy to find the most specific registered handler. If no handler exists for a specific subclass (such as DatabaseOperationError), FastAPI invokes the handler for its nearest ancestor, OpenNotebookError, resulting in a 500 response.

Practical Implementation Example

Services raise domain exceptions without worrying about HTTP details, while routers automatically receive appropriate JSON responses.


# src/open_notebook/services/some_service.py

from open_notebook.exceptions import NotFoundError, InvalidInputError

def get_notebook(notebook_id: str):
    notebook = db.fetch(notebook_id)
    if notebook is None:
        # This will be turned into a 404 response

        raise NotFoundError(f"Notebook {notebook_id} not found")
    if not isinstance(notebook_id, str):
        # This will be turned into a 400 response

        raise InvalidInputError("Notebook ID must be a string")
    return notebook

When the endpoint calls this service:


# src/api/routers/notebooks.py

from fastapi import APIRouter, Depends
from open_notebook.services.notebook_service import get_notebook

router = APIRouter()

@router.get("/notebooks/{notebook_id}")
async def read_notebook(notebook_id: str):
    return get_notebook(notebook_id)

If get_notebook raises NotFoundError, FastAPI returns:

{
  "detail": "Notebook abc123 not found"
}

with HTTP status 404 and appropriate CORS headers.

Summary

  • Open Notebook defines a custom exception hierarchy in open_notebook/exceptions.py with OpenNotebookError as the base class.
  • FastAPI handlers in api/main.py map specific exceptions to HTTP status codes: 404 for not found, 400 for invalid input, 401 for authentication failures, 429 for rate limits, 422 for configuration errors, and 502 for external service issues.
  • Unhandled subclasses like DatabaseOperationError fall back to the base handler, returning 500 internal server errors.
  • This separation allows business logic to raise domain-specific exceptions while the API layer handles HTTP translation automatically.

Frequently Asked Questions

What happens if I raise an exception that isn't registered in api/main.py?

If you raise a subclass of OpenNotebookError that lacks a specific handler (such as DatabaseOperationError or FileOperationError), FastAPI will use the base OpenNotebookError handler registered at lines 80‑86, returning a 500 Internal Server Error with JSON detail.

Why does ConfigurationError return 422 instead of 500?

The ConfigurationError maps to 422 Unprocessable Entity (lines 53‑59) because it typically indicates a semantic error in the request or environment setup that the client might be able to correct, rather than a catastrophic server failure.

How can I add a custom exception handler for a new exception type?

Define your exception in open_notebook/exceptions.py inheriting from OpenNotebookError, then add a new @app.exception_handler decorator in api/main.py following the pattern used for existing handlers (lines 17‑77), returning the appropriate status code and CORS headers.

Do these exception handlers include CORS headers in the response?

Yes, according to the source code in api/main.py, each exception handler returns responses that include appropriate CORS headers, ensuring browser clients can read the error responses even when making cross-origin requests.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →