Custom Exemptions
While decorators provide a simple way to exempt specific routes from maintenance mode, more complex scenarios often require custom exemption logic. The exempt_handler parameter of the middleware allows you to implement custom rules that can consider any aspect of the incoming request.
Docs Bypass Maintenance
By default, FastAPI's built-in documentation endpoints (/docs, /redoc, /openapi.json, /docs/oauth2-redirect) are automatically exempted from maintenance mode to keep API documentation accessible. This built-in behavior works alongside any custom exemption handler you define.
HTTP Error Behavior
The middleware automatically exempts requests that would result in HTTP errors (e.g.,404 Not Found, 405 Method Not Allowed, etc.) from maintenance mode. This ensures that when clients make requests to non-existent paths or use wrong HTTP methods, they receive the proper error response instead of a maintenance response.
Basic Usage
Here's a simple example that exempts health check endpoints and requests with an admin key from maintenance mode:
from fastapi import FastAPI, Request
from fastapi_maintenance import MaintenanceModeMiddleware
def is_exempt(request: Request) -> bool:
# Exempt all paths starting with "/health"
if request.url.path.startswith("/health"):
return True
# Exempt requests with special header
if request.headers.get("X-Admin-Key") == "supersecret":
return True
return False
app = FastAPI()
app.add_middleware(
MaintenanceModeMiddleware,
exempt_handler=is_exempt
)
# Result during maintenance:
# ✅ /docs, /redoc, /openapi.json, /docs/oauth2-redirect (automatically exempt)
# ✅ /health/* (custom exemption)
# ✅ Requests with X-Admin-Key header (custom exemption)
# ❌ Other endpoints return 503
Handler Function Requirements
The exempt handler is a function that decides whether a request should bypass maintenance mode. Here's what you need to know:
Function Signature
The handler function must:
- Accept a single Request parameter from FastAPI
- Return a bool value
- Be either synchronous or asynchronous (both are supported)
# Synchronous handler
def is_exempt(request: Request) -> bool:
# Logic here
return True/False
# Asynchronous handler
async def is_exempt(request: Request) -> bool:
# Async logic here
return True/False
Return Value
The return value determines how the request is handled:
- True: The request bypasses maintenance mode and proceeds normally
- False: The request is subject to maintenance mode rules
Execution Context
The handler runs for every request when maintenance middleware is in place, so:
- Keep it lightweight to avoid performance issues
- Handle all exceptions internally
- Avoid side effects that could impact other requests
- The handler can access any aspect of the Request object (headers, path, query params, etc.)
Examples
Role-Based Exemptions
def is_exempt(request: Request) -> bool:
# Check for admin role in JWT token
token = request.headers.get("Authorization", "").replace("Bearer ", "")
try:
payload = decode_jwt(token)
if "admin" in payload.get("roles", []):
return True
except Exception:
# Token validation failed, no exemption
pass
return False
IP-Based Exemptions
def is_exempt(request: Request) -> bool:
# Exempt internal networks
client_host = request.client.host if request.client else None
# Exempt localhost and internal network
if client_host in ["127.0.0.1", "::1"] or client_host.startswith("10."):
return True
return False
Combining Multiple Conditions
def is_exempt(request: Request) -> bool:
# Health check endpoints are always accessible
if request.url.path.startswith("/health"):
return True
# Read-only operations during partial maintenance
if request.method in ["GET", "HEAD"] and not is_full_maintenance():
return True
# Admin users can access everything
if is_admin_user(request):
return True
return False
Async Support
The exempt handler can also be asynchronous:
async def is_exempt(request: Request) -> bool:
# Perform async operations like database queries
user = await get_user_from_db(request.headers.get("X-User-ID"))
if user and user.is_admin:
return True
return False
app.add_middleware(
MaintenanceModeMiddleware,
exempt_handler=is_exempt
)