"""Redis-backed fixed-window rate limiter helpers for sensitive API operations.""" import time from redis.exceptions import RedisError from app.worker.queue import get_redis def _rate_limit_key(*, scope: str, subject: str, window_id: int) -> str: """Builds a stable Redis key for one scope, subject, and fixed time window.""" return f"dcm:rate-limit:{scope}:{subject}:{window_id}" def increment_rate_limit( *, scope: str, subject: str, limit: int, window_seconds: int = 60, ) -> tuple[int, int]: """Increments one rate bucket and returns current count with configured limit.""" bounded_limit = max(0, int(limit)) if bounded_limit == 0: return (0, 0) bounded_window = max(1, int(window_seconds)) current_window = int(time.time() // bounded_window) key = _rate_limit_key(scope=scope, subject=subject, window_id=current_window) redis_client = get_redis() try: pipeline = redis_client.pipeline(transaction=True) pipeline.incr(key, 1) pipeline.expire(key, bounded_window + 5) count_value, _ = pipeline.execute() except RedisError as error: raise RuntimeError("Rate limiter backend unavailable") from error return (int(count_value), bounded_limit)