RAGChatbot / api /rate_limiter.py
Shurem's picture
add personlize agent
a5ddf46
Raw
History Blame Contribute Delete
3.75 kB
"""
Rate limiting utility for the Personalization API.
Implements an in-memory sliding window rate limiter.
"""
import time
from dataclasses import dataclass, field
from typing import Dict, Optional, Tuple
from config import Config
@dataclass
class RateLimitEntry:
"""
Entry in the rate limit store tracking request count and reset time.
"""
count: int = 0
reset_time: float = 0.0
class RateLimiter:
"""
In-memory rate limiter using sliding window algorithm.
Tracks requests per user and enforces rate limits.
"""
def __init__(
self,
max_requests: Optional[int] = None,
window_seconds: Optional[int] = None
):
"""
Initialize rate limiter.
Args:
max_requests: Maximum requests per window (default from config)
window_seconds: Window duration in seconds (default from config)
"""
self.max_requests = max_requests or Config.PERSONALIZE_RATE_LIMIT
self.window_seconds = window_seconds or Config.PERSONALIZE_RATE_LIMIT_WINDOW
self._store: Dict[str, RateLimitEntry] = {}
def check(self, user_id: str) -> Tuple[bool, Optional[int]]:
"""
Check if a request is allowed for the given user.
Args:
user_id: Unique identifier for the user
Returns:
Tuple of (is_allowed, retry_after_seconds)
- If allowed: (True, None)
- If rate limited: (False, seconds_until_reset)
"""
now = time.time()
entry = self._store.get(user_id)
# No entry or window expired - create new window
if not entry or now > entry.reset_time:
self._store[user_id] = RateLimitEntry(
count=1,
reset_time=now + self.window_seconds
)
return (True, None)
# Check if over limit
if entry.count >= self.max_requests:
retry_after = int(entry.reset_time - now) + 1 # Add 1 to ensure we're past reset
return (False, retry_after)
# Increment count
entry.count += 1
return (True, None)
def get_remaining(self, user_id: str) -> int:
"""
Get remaining requests for the user in current window.
Args:
user_id: Unique identifier for the user
Returns:
Number of remaining requests
"""
now = time.time()
entry = self._store.get(user_id)
if not entry or now > entry.reset_time:
return self.max_requests
return max(0, self.max_requests - entry.count)
def reset(self, user_id: str) -> None:
"""
Reset rate limit for a user (useful for testing).
Args:
user_id: Unique identifier for the user
"""
if user_id in self._store:
del self._store[user_id]
def clear_expired(self) -> int:
"""
Clear all expired entries from the store.
Returns:
Number of entries cleared
"""
now = time.time()
expired_keys = [
key for key, entry in self._store.items()
if now > entry.reset_time
]
for key in expired_keys:
del self._store[key]
return len(expired_keys)
# Global rate limiter instance for personalization endpoint
_personalize_rate_limiter: Optional[RateLimiter] = None
def get_personalize_rate_limiter() -> RateLimiter:
"""
Get or create the global rate limiter instance.
Returns:
RateLimiter instance
"""
global _personalize_rate_limiter
if _personalize_rate_limiter is None:
_personalize_rate_limiter = RateLimiter()
return _personalize_rate_limiter