Перейти к содержанию

Exception Handlers

Warning

The current page still doesn't have a translation for this language.

But you can help translating it: Contributing.

Exception handlers let you control how your application responds to errors. Instead of default error messages, you can return custom responses, log errors, or transform exceptions into user-friendly formats.

What You'll Learn

  • Creating custom exception handlers
  • Applying handlers at different levels
  • Handler priority and precedence
  • Using built-in handlers
  • Transforming errors into JSON

Quick Start

from ravyn import Ravyn, get
from ravyn.exceptions import NotFound
from ravyn.responses import JSONResponse

def handle_not_found(request, exc):
    return JSONResponse(
        {"error": "Resource not found", "detail": str(exc)},
        status_code=404
    )

app = Ravyn(
    exception_handlers={
        NotFound: handle_not_found
    }
)

@get("/users/{user_id}")
def get_user(user_id: int) -> dict:
    raise NotFound(f"User {user_id} not found")
    # Returns: {"error": "Resource not found", "detail": "User 123 not found"}

Why Use Exception Handlers?

Perfect For:

  • Custom Error Formats - Return consistent error structures

  • Error Logging - Log errors before returning response

  • User-Friendly Messages - Transform technical errors

  • Error Tracking - Send errors to monitoring services

  • Validation Errors - Format validation errors consistently


Creating Exception Handlers

Exception handlers are functions that take request and exc parameters:

def my_handler(request, exc):
    # request: The current request
    # exc: The exception that was raised
    return JSONResponse({"error": str(exc)}, status_code=500)

Basic Example

from ravyn import Ravyn
from ravyn.exceptions import ValidationError
from ravyn.responses import JSONResponse

def validation_handler(request, exc):
    return JSONResponse(
        {
            "error": "Validation failed",
            "details": exc.detail
        },
        status_code=400
    )

app = Ravyn(
    exception_handlers={
        ValidationError: validation_handler
    }
)

Exception Handler Levels

Exception handlers can be applied at multiple levels with clear precedence rules.

Application Level

Handles exceptions across the entire app:

from ravyn import Ravyn
from ravyn.exceptions import NotFound

def app_not_found_handler(request, exc):
    return JSONResponse({"error": "Not found at app level"}, status_code=404)

app = Ravyn(
    exception_handlers={
        NotFound: app_not_found_handler
    }
)

Gateway Level

Overrides application-level handlers for specific routes:

from ravyn import Ravyn, Gateway, get
from ravyn.exceptions import ValidationError

def gateway_validation_handler(request, exc):
    return JSONResponse({"error": "Gateway validation error"}, status_code=400)

@get("/users")
def list_users() -> dict:
    raise ValidationError("Invalid request")

app = Ravyn(routes=[
    Gateway(
        "/users",
        handler=list_users,
        exception_handlers={
            ValidationError: gateway_validation_handler
        }
    )
])

Handler Level

Most specific. overrides all other handlers:

from ravyn import get
from ravyn.exceptions import ValidationError

def handler_validation_handler(request, exc):
    return JSONResponse({"error": "Handler-specific error"}, status_code=400)

@get(
    "/users",
    exception_handlers={
        ValidationError: handler_validation_handler
    }
)
def list_users() -> dict:
    raise ValidationError("Error")

Handler Precedence

When the same exception is handled at multiple levels, the most specific handler wins:

Handler Level (highest priority)
  ↓
Gateway Level
  ↓
Include Level
  ↓
Application Level (lowest priority)

Example

from ravyn import Ravyn, Gateway, get
from ravyn.exceptions import NotFound

# Application level
def app_handler(request, exc):
    return JSONResponse({"level": "app"}, status_code=404)

# Gateway level
def gateway_handler(request, exc):
    return JSONResponse({"level": "gateway"}, status_code=404)

# Handler level
def handler_handler(request, exc):
    return JSONResponse({"level": "handler"}, status_code=404)

@get(
    "/users/{id}",
    exception_handlers={NotFound: handler_handler}
)
def get_user(id: int) -> dict:
    raise NotFound("User not found")

app = Ravyn(
    routes=[
        Gateway(
            "/users/{id}",
            handler=get_user,
            exception_handlers={NotFound: gateway_handler}
        )
    ],
    exception_handlers={NotFound: app_handler}
)

# Result: Uses handler_handler (most specific)

Built-In Exception Handlers

Ravyn provides ready-to-use handlers:

value_error_handler

Converts ValueError to JSON:

from ravyn import Ravyn
from ravyn.exception_handlers import value_error_handler

app = Ravyn(
    exception_handlers={
        ValueError: value_error_handler
    }
)

@app.get("/divide")
def divide(a: int, b: int) -> dict:
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return {"result": a / b}

# GET /divide?a=10&b=0
# Returns: {"detail": "Cannot divide by zero"}

Practical Examples

Example 1: Error Logging

from ravyn import Ravyn
from ravyn.exceptions import HTTPException
from ravyn.responses import JSONResponse
import logging

logger = logging.getLogger(__name__)

def log_and_return_error(request, exc):
    # Log the error
    logger.error(
        f"Error on {request.url.path}: {str(exc)}",
        exc_info=True
    )

    # Return user-friendly response
    return JSONResponse(
        {"error": "An error occurred", "message": str(exc)},
        status_code=exc.status_code if hasattr(exc, 'status_code') else 500
    )

app = Ravyn(
    exception_handlers={
        HTTPException: log_and_return_error
    }
)

Example 2: Validation Error Formatting

from ravyn import Ravyn, post
from ravyn.exceptions import ValidationError
from ravyn.responses import JSONResponse
from pydantic import BaseModel, ValidationError as PydanticValidationError

def format_validation_errors(request, exc):
    if isinstance(exc.detail, dict):
        # Already formatted
        errors = exc.detail
    else:
        # Convert to dict
        errors = {"message": str(exc.detail)}

    return JSONResponse(
        {
            "error": "Validation failed",
            "fields": errors
        },
        status_code=400
    )

app = Ravyn(
    exception_handlers={
        ValidationError: format_validation_errors
    }
)

class User(BaseModel):
    name: str
    email: str

@post("/users")
def create_user(user: User) -> dict:
    return {"created": user.dict()}

Example 3: Error Tracking Integration

from ravyn import Ravyn
from ravyn.exceptions import HTTPException
from ravyn.responses import JSONResponse
import sentry_sdk

def track_and_handle_error(request, exc):
    # Send to Sentry
    sentry_sdk.capture_exception(exc)

    # Return response
    return JSONResponse(
        {
            "error": "Internal server error",
            "message": "This error has been logged"
        },
        status_code=500
    )

app = Ravyn(
    exception_handlers={
        Exception: track_and_handle_error
    }
)

Common Pitfalls & Fixes

Pitfall 1: Handler Doesn't Return Response

Problem: Exception handler doesn't return a response object.

# Wrong - no return
def bad_handler(request, exc):
    print(f"Error: {exc}")
    # Missing return!

Solution: Always return a Response:

# Correct
def good_handler(request, exc):
    print(f"Error: {exc}")
    return JSONResponse({"error": str(exc)}, status_code=500)

Pitfall 2: Wrong Exception Type

Problem: Handler registered for wrong exception class.

# Wrong - ValidationError won't catch NotFound
app = Ravyn(
    exception_handlers={
        ValidationError: my_handler
    }
)

@app.get("/users/{id}")
def get_user(id: int) -> dict:
    raise NotFound("User not found")  # Not caught!

Solution: Register handler for correct exception:

# Correct
app = Ravyn(
    exception_handlers={
        NotFound: my_handler
    }
)

Pitfall 3: Forgetting Request Parameter

Problem: Handler signature is wrong.

# Wrong - missing request parameter
def bad_handler(exc):
    return JSONResponse({"error": str(exc)})

Solution: Include both request and exc:

# Correct
def good_handler(request, exc):
    return JSONResponse({"error": str(exc)})

Pitfall 4: Not Understanding Precedence

Problem: Expecting app-level handler but gateway-level takes precedence.

# Confusing - which handler runs?
app = Ravyn(
    routes=[
        Gateway(
            "/users",
            handler=get_users,
            exception_handlers={NotFound: gateway_handler}
        )
    ],
    exception_handlers={NotFound: app_handler}
)
# gateway_handler wins (more specific)

Solution: Understand precedence: Handler > Gateway > Include > App


Exception Handler Patterns

Pattern 1: Consistent Error Format

def standard_error_handler(request, exc):
    return JSONResponse(
        {
            "success": False,
            "error": {
                "type": exc.__class__.__name__,
                "message": str(exc),
                "path": request.url.path
            }
        },
        status_code=getattr(exc, 'status_code', 500)
    )

Pattern 2: Development vs Production

from ravyn import RavynSettings

class Settings(RavynSettings):
    debug: bool = False

    @property
    def exception_handlers(self):
        if self.debug:
            # Detailed errors in development
            return {Exception: detailed_error_handler}
        else:
            # Generic errors in production
            return {Exception: generic_error_handler}

Pattern 3: Multiple Exception Types

from ravyn.exceptions import NotFound, PermissionDenied, ValidationError

def not_found_handler(request, exc):
    return JSONResponse({"error": "Not found"}, status_code=404)

def permission_handler(request, exc):
    return JSONResponse({"error": "Access denied"}, status_code=403)

def validation_handler(request, exc):
    return JSONResponse({"error": "Invalid data"}, status_code=400)

app = Ravyn(
    exception_handlers={
        NotFound: not_found_handler,
        PermissionDenied: permission_handler,
        ValidationError: validation_handler
    }
)

Next Steps

Now that you understand exception handlers, explore: