Introduction
Proper error handling ensures robust applications with clear error messages. This tutorial covers custom exceptions, standardized error responses, and error handling in Flask and FastAPI.
Custom Exceptions
# exceptions.py
class AppException(Exception):
"""Base exception for application errors."""
def __init__(self, message: str, status_code: int = 500, details: dict = None):
self.message = message
self.status_code = status_code
self.details = details
super().__init__(self.message)
class NotFoundError(AppException):
"""Resource not found."""
def __init__(self, resource: str, identifier: str):
super().__init__(
message=f"{resource} not found",
status_code=404,
details={"resource": resource, "identifier": identifier}
)
class ValidationError(AppException):
"""Validation error."""
def __init__(self, message: str, errors: list = None):
super().__init__(
message=message,
status_code=400,
details={"errors": errors or []}
)
class AuthenticationError(AppException):
"""Authentication failed."""
def __init__(self, message: str = "Authentication required"):
super().__init__(message=message, status_code=401)
class AuthorizationError(AppException):
"""Authorization failed."""
def __init__(self, message: str = "Access denied"):
super().__init__(message=message, status_code=403)
class ConflictError(AppException):
"""Resource conflict."""
def __init__(self, message: str, resource_id: str = None):
super().__init__(
message=message,
status_code=409,
details={"resource_id": resource_id}
)
# Usage
def get_user(user_id: int):
user = find_user(user_id)
if not user:
raise NotFoundError("User", user_id)
return user
Error Response Format
# response.py
from typing import Any, Optional, List
from pydantic import BaseModel
from datetime import datetime
class ErrorResponse(BaseModel):
"""Standard error response format."""
success: bool = False
error: str
message: str
status_code: int
timestamp: datetime = datetime.utcnow()
details: Optional[dict] = None
errors: Optional[List[dict]] = None
request_id: Optional[str] = None
def error_response(
error: str,
message: str,
status_code: int,
details: dict = None,
errors: list = None,
request_id: str = None
) -> dict:
"""Create standardized error response."""
return ErrorResponse(
error=error,
message=message,
status_code=status_code,
details=details,
errors=errors,
request_id=request_id
).dict()
# Usage examples
# 400 Bad Request
return error_response(
error="ValidationError",
message="Invalid input data",
status_code=400,
errors=[{"field": "email", "error": "Invalid email format"}]
)
# 404 Not Found
return error_response(
error="NotFound",
message="User not found",
status_code=404,
details={"resource": "user", "id": 123}
)
FastAPI Exception Handlers
# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content=error_response(
error=type(exc).__name__,
message=exc.message,
status_code=exc.status_code,
details=exc.details
)
)
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content=error_response(
error="ValueError",
message=str(exc),
status_code=400
)
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content=error_response(
error="InternalServerError",
message="An unexpected error occurred",
status_code=500,
details={"path": str(request.url)}
)
)
Flask Error Handling
# app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.errorhandler(AppException)
def handle_app_exception(error):
response = error_response(
error=type(error).__name__,
message=error.message,
status_code=error.status_code,
details=error.details
)
return jsonify(response), error.status_code
@app.errorhandler(404)
def handle_not_found(error):
return jsonify(error_response(
error="NotFound",
message="Resource not found",
status_code=404
)), 404
@app.errorhandler(500)
def handle_server_error(error):
return jsonify(error_response(
error="InternalServerError",
message="Server error",
status_code=500
)), 500
Practice Problems
- Create a custom exception for database operations
- Implement error logging alongside error responses
- Add error codes for different error types
- Create exception handlers for async operations
- Implement retry logic for transient errors