Errors API Reference¶
Complete reference for error handling classes in FastOpenAPI.
Error Class Hierarchy¶
APIError (base)
├── BadRequestError
├── ValidationError
├── ResourceNotFoundError
├── AuthenticationError
├── AuthorizationError
├── ResourceConflictError
├── InternalServerError
│ └── DependencyError
│ ├── CircularDependencyError
│ └── SecurityError
└── ServiceUnavailableError
APIError¶
Base exception class for all API errors.
Class Definition¶
class APIError(Exception):
status_code = HTTPStatus.INTERNAL_SERVER_ERROR
default_message = "An error occurred"
error_type = ErrorType.INTERNAL_SERVER_ERROR
def __init__(
self,
message: str | None = None,
details: Any | None = None,
)
Attributes¶
- status_code (
HTTPStatus): HTTP status code (default:500) - default_message (
str): Default error message - error_type (
ErrorType): Error type identifier - message (
str): Error message - details (
Any): Additional error details
Methods¶
to_response()¶
Convert to standardized error response.
Returns: Standardized error dictionary
Example:
error = BadRequestError("Invalid input")
response = error.to_response()
# {
# "error": {
# "type": "bad_request",
# "message": "Invalid input",
# "status": 400
# }
# }
from_exception()¶
Convert any exception to an APIError.
@classmethod
def from_exception(
exc: Exception,
mapper: dict[type[Exception], type[APIError]] | None = None,
) -> APIError
Parameters:
- exc: Exception to convert
- mapper: Optional mapping of exception types to APIError subclasses
Returns: APIError instance
Example:
try:
# Some code that may raise ValueError
raise ValueError("Invalid value")
except Exception as e:
api_error = APIError.from_exception(e)
# Returns APIError with message "Invalid value" and status 500
BadRequestError¶
HTTP 400 - Bad Request
Definition¶
class BadRequestError(APIError):
status_code = HTTPStatus.BAD_REQUEST # 400
default_message = "Bad request"
error_type = ErrorType.BAD_REQUEST
Usage¶
@router.post("/items")
def create_item(name: str = Body(...)):
if not name.strip():
raise BadRequestError("Item name cannot be empty")
return {"name": name}
Response Format¶
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"type": "bad_request",
"message": "Item name cannot be empty",
"status": 400
}
}
ValidationError¶
HTTP 422 - Unprocessable Entity
Definition¶
class ValidationError(APIError):
status_code = HTTPStatus.UNPROCESSABLE_ENTITY # 422
default_message = "Validation error"
error_type = ErrorType.VALIDATION_ERROR
Usage¶
@router.post("/items")
def create_item(price: float):
if price < 0:
raise ValidationError(
message="Price must be positive",
details={"field": "price", "value": price}
)
return {"price": price}
Response Format¶
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"type": "validation_error",
"message": "Validation error",
"status": 422,
"details": "Value is not a valid email address"
}
}
ResourceNotFoundError¶
HTTP 404 - Not Found
Definition¶
class ResourceNotFoundError(APIError):
status_code = HTTPStatus.NOT_FOUND # 404
default_message = "Resource not found"
error_type = ErrorType.RESOURCE_NOT_FOUND
Usage¶
@router.get("/users/{user_id}")
def get_user(user_id: int):
user = database.get_user(user_id)
if not user:
raise ResourceNotFoundError(f"User {user_id} not found")
return user
Response Format¶
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"type": "resource_not_found",
"message": "User 123 not found",
"status": 404
}
}
AuthenticationError¶
HTTP 401 - Unauthorized
Definition¶
class AuthenticationError(APIError):
status_code = HTTPStatus.UNAUTHORIZED # 401
default_message = "Authentication required"
error_type = ErrorType.AUTHENTICATION_ERROR
Usage¶
def get_current_user(token: str = Header(..., alias="Authorization")):
if not token.startswith("Bearer "):
raise AuthenticationError("Invalid token format")
user = verify_token(token[7:])
if not user:
raise AuthenticationError("Invalid or expired token")
return user
@router.get("/profile")
def get_profile(user = Depends(get_current_user)):
return user
Response Format¶
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": {
"type": "authentication_error",
"message": "Invalid or expired token",
"status": 401
}
}
AuthorizationError¶
HTTP 403 - Forbidden
Definition¶
class AuthorizationError(APIError):
status_code = HTTPStatus.FORBIDDEN # 403
default_message = "Permission denied"
error_type = ErrorType.AUTHORIZATION_ERROR
Usage¶
def require_admin(current_user = Depends(get_current_user)):
if not current_user.is_admin:
raise AuthorizationError("Admin access required")
return current_user
@router.delete("/users/{user_id}")
def delete_user(user_id: int, admin = Depends(require_admin)):
database.delete_user(user_id)
return {"deleted": user_id}
Response Format¶
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": {
"type": "authorization_error",
"message": "Admin access required",
"status": 403
}
}
ResourceConflictError¶
HTTP 409 - Conflict
Definition¶
class ResourceConflictError(APIError):
status_code = HTTPStatus.CONFLICT # 409
default_message = "Resource conflict"
error_type = ErrorType.RESOURCE_CONFLICT
Usage¶
@router.post("/users")
def create_user(username: str, email: str):
if database.user_exists(username):
raise ResourceConflictError(
f"Username '{username}' already exists"
)
if database.email_exists(email):
raise ResourceConflictError(
f"Email '{email}' already registered"
)
user = database.create_user(username, email)
return user
Response Format¶
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"type": "resource_conflict",
"message": "Username 'john' already exists",
"status": 409
}
}
InternalServerError¶
HTTP 500 - Internal Server Error
Definition¶
class InternalServerError(APIError):
status_code = HTTPStatus.INTERNAL_SERVER_ERROR # 500
default_message = "Internal server error"
error_type = ErrorType.INTERNAL_SERVER_ERROR
Usage¶
@router.get("/items")
def list_items():
try:
items = database.get_all_items()
return items
except DatabaseError as e:
raise InternalServerError(
message="Database error occurred",
details=str(e)
)
Response Format¶
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": {
"type": "internal_server_error",
"message": "Database error occurred",
"status": 500,
"details": "Connection timeout"
}
}
ServiceUnavailableError¶
HTTP 503 - Service Unavailable
Definition¶
class ServiceUnavailableError(APIError):
status_code = HTTPStatus.SERVICE_UNAVAILABLE # 503
default_message = "Service unavailable"
error_type = ErrorType.SERVICE_UNAVAILABLE
Usage¶
@router.get("/items")
def list_items():
if not database.is_connected():
raise ServiceUnavailableError("Database is unavailable")
return database.get_all_items()
Dependency Errors¶
DependencyError¶
Base exception for dependency resolution errors.
CircularDependencyError¶
Raised when circular dependencies are detected.
SecurityError¶
Raised when security requirements are not met.
class SecurityError(DependencyError):
status_code = HTTPStatus.FORBIDDEN # 403
default_message = "insufficient_scope"
error_type = ErrorType.AUTHORIZATION_ERROR
Exception Mapping¶
Map framework-specific exceptions to API errors:
from fastopenapi.routers.base import BaseAdapter
# Custom exception mapper
EXCEPTION_MAPPER = {
ValueError: BadRequestError,
KeyError: ResourceNotFoundError,
PermissionError: AuthorizationError,
}
class MyRouter(BaseAdapter):
EXCEPTION_MAPPER = EXCEPTION_MAPPER
STATUS_TO_ERROR_TYPE¶
A mapping of HTTP status codes to error types, used internally for exception conversion:
from fastopenapi.errors.exceptions import STATUS_TO_ERROR_TYPE
# STATUS_TO_ERROR_TYPE = {
# HTTPStatus.BAD_REQUEST: ErrorType.BAD_REQUEST,
# HTTPStatus.UNAUTHORIZED: ErrorType.AUTHENTICATION_ERROR,
# HTTPStatus.FORBIDDEN: ErrorType.AUTHORIZATION_ERROR,
# HTTPStatus.NOT_FOUND: ErrorType.RESOURCE_NOT_FOUND,
# HTTPStatus.CONFLICT: ErrorType.RESOURCE_CONFLICT,
# HTTPStatus.UNPROCESSABLE_ENTITY: ErrorType.VALIDATION_ERROR,
# HTTPStatus.INTERNAL_SERVER_ERROR: ErrorType.INTERNAL_SERVER_ERROR,
# HTTPStatus.SERVICE_UNAVAILABLE: ErrorType.SERVICE_UNAVAILABLE,
# }
This is used by APIError.from_exception() to determine the error type when converting framework exceptions.
Custom Errors¶
Create custom error classes:
from fastopenapi.errors import APIError
from http import HTTPStatus
class RateLimitError(APIError):
status_code = HTTPStatus.TOO_MANY_REQUESTS # 429
default_message = "Rate limit exceeded"
error_type = "rate_limit_error"
@router.get("/api/data")
def get_data():
if rate_limiter.is_exceeded():
raise RateLimitError("Too many requests. Try again later.")
return {"data": "..."}
Error Response Structure¶
All errors follow this structure:
{
"error": {
"type": "error_type_identifier",
"message": "Human-readable error message",
"status": 400,
"details": "Optional additional details (string, included only when provided)"
}
}
Best Practices¶
1. Use Specific Error Classes¶
# Good
raise ResourceNotFoundError(f"User {user_id} not found")
# Avoid
raise Exception("User not found")
2. Provide Helpful Messages¶
# Good
raise BadRequestError("Email format is invalid. Expected: user@example.com")
# Avoid
raise BadRequestError("Invalid input")
3. Include Details for Validation Errors¶
raise ValidationError(
message="Validation failed",
details=[
{"field": "email", "error": "Invalid format"},
{"field": "age", "error": "Must be >= 18"}
]
)
4. Don't Expose Sensitive Information¶
# Good
raise InternalServerError("Database error occurred")
# Avoid
raise InternalServerError(f"SQL error: {sql_exception.query}")
See Also¶
- Error Handling Guide - Error handling patterns
- Security Guide - Authentication and authorization
- Dependencies Reference - Dependency injection