"""In-memory sliding window rate limiter. Note: For multi-instance deployments, swap to Redis-backed implementation. """ import asyncio import time from collections import defaultdict from fastapi import Request, HTTPException, status from app.config import settings _requests: dict[str, list[float]] = defaultdict(list) _lock = asyncio.Lock() async def check_rate_limit(request: Request) -> None: """Check if the request IP is within rate limits. Raises 429 if exceeded.""" client_ip = request.client.host if request.client else "unknown" now = time.time() window = settings.RATE_LIMIT_WINDOW_SECONDS max_requests = settings.RATE_LIMIT_REQUESTS async with _lock: # Clean old entries _requests[client_ip] = [t for t in _requests[client_ip] if t > now - window] if len(_requests[client_ip]) >= max_requests: raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests. Please try again later.", ) _requests[client_ip].append(now) # Evict empty keys to prevent unbounded growth stale = [ip for ip, ts in _requests.items() if not ts] for ip in stale: del _requests[ip]