"""Простой in-memory rate limit (на процесс).""" from __future__ import annotations import time from collections import defaultdict from threading import Lock class RateLimiter: def __init__(self) -> None: self._hits: dict[str, list[float]] = defaultdict(list) self._lock = Lock() def hit(self, key: str, *, limit: int, window_sec: float) -> bool: """True если запрос разрешён, False если превышен лимит.""" now = time.monotonic() with self._lock: bucket = self._hits[key] cutoff = now - window_sec self._hits[key] = [t for t in bucket if t >= cutoff] if len(self._hits[key]) >= limit: return False self._hits[key].append(now) return True limiter = RateLimiter() def client_ip(request) -> str: forwarded = (request.headers.get("X-Forwarded-For") or "").split(",")[0].strip() if forwarded: return forwarded peer = request.remote return str(peer or "unknown") def allow(request, bucket: str, *, limit: int = 20, window_sec: float = 60.0) -> bool: ip = client_ip(request) return limiter.hit(f"{bucket}:{ip}", limit=limit, window_sec=window_sec)