Spaces:
Sleeping
Sleeping
| 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] | |
| 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) |