BrejBala's picture
Deploy backend Docker app
e63c592
from typing import Any
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address
from app.core.config import get_settings
from app.core.logging import get_logger
logger = get_logger(__name__)
# Global limiter instance used for decorators.
limiter = Limiter(key_func=get_remote_address)
def setup_rate_limiter(app: FastAPI) -> None:
"""Configure SlowAPI rate limiting middleware and handlers.
Limits are enabled/disabled via Settings.RATE_LIMIT_ENABLED.
"""
settings = get_settings()
if not getattr(settings, "RATE_LIMIT_ENABLED", True):
logger.info("Rate limiting is disabled via settings.")
return
logger.info("Rate limiting enabled with SlowAPI.")
app.state.limiter = limiter # type: ignore[attr-defined]
@app.exception_handler(RateLimitExceeded)
async def rate_limit_exceeded_handler( # type: ignore[no-redef]
request: Request,
exc: RateLimitExceeded,
) -> JSONResponse:
retry_after: str | None = None
try:
retry_after = exc.headers.get("Retry-After") # type: ignore[assignment]
except Exception: # noqa: BLE001
retry_after = None
logger.warning(
"Rate limit exceeded path=%s client=%s limit=%s",
request.url.path,
get_remote_address(request),
exc.detail,
)
content: dict[str, Any] = {
"detail": "Rate limit exceeded. Please slow down your requests.",
}
if retry_after is not None:
content["retry_after"] = retry_after
return JSONResponse(status_code=429, content=content)
# Attach SlowAPI middleware
app.add_middleware(SlowAPIMiddleware)