Your first decorator¶
This tutorial shows you how to put a single route into maintenance mode and verify the behaviour. The examples use FastAPI — the currently supported ASGI adapter.
1. Create a simple app¶
from fastapi import FastAPI
from shield.core.engine import ShieldEngine
from shield.core.backends.memory import MemoryBackend
from shield.fastapi.middleware import ShieldMiddleware
from shield.fastapi.router import ShieldRouter
from shield.fastapi.decorators import maintenance, force_active
engine = ShieldEngine(backend=MemoryBackend())
app = FastAPI()
app.add_middleware(ShieldMiddleware, engine=engine)
router = ShieldRouter(engine=engine)
@router.get("/payments")
@maintenance(reason="Database migration — back at 04:00 UTC")
async def get_payments():
return {"payments": []}
@router.get("/health")
@force_active
async def health():
return {"status": "ok"}
app.include_router(router)
2. Run it¶
3. Verify the behaviour¶
{
"error": {
"code": "MAINTENANCE_MODE",
"message": "This endpoint is temporarily unavailable",
"reason": "Database migration — back at 04:00 UTC",
"path": "GET:/payments",
"retry_after": null
}
}
How it works¶
-
@maintenance(...)stamps__shield_meta__ = {"status": "maintenance", "reason": "..."}on the function. The function itself is not modified; it still runs normally if called directly. -
When
app.include_router(router)is called,ShieldRouterscans all routes for__shield_meta__and callsengine.register()for each one. -
On every HTTP request,
ShieldMiddlewarecallsengine.check(path). If the route is in maintenance, the engine raisesMaintenanceExceptionand the middleware returns a 503 response. The route handler never executes.
Available decorators¶
| Decorator | Behaviour |
|---|---|
@maintenance(reason, start, end) |
503, temporarily unavailable |
@disabled(reason) |
503, permanently off |
@env_only("dev", "staging") |
404 in other environments |
@deprecated(sunset, use_instead) |
200 + deprecation headers |
@force_active |
Always 200, bypasses all checks |
@rate_limit("100/minute") |
429 when the limit is exceeded; requires api-shield[rate-limit] |
Runtime changes without restart¶
Once the middleware is in place, you can change route state at runtime with no code changes and no restart:
# Enable the route programmatically
await engine.enable("GET:/payments")
# Put it back in maintenance
await engine.set_maintenance("GET:/payments", reason="Second migration wave")
Or via the CLI (requires ShieldAdmin mounted; see Admin Dashboard):