Spaces:
Sleeping
Sleeping
import asyncio | |
import pytz | |
from datetime import datetime, timedelta | |
import time | |
from typing import Dict, Optional | |
import logging | |
from utils.logging_utils import log_to_file | |
from utils.admin import supabase # Ajoutez cet import avec les autres | |
from utils.cache_manager import CacheManager | |
cache_manager = CacheManager() # optimiser les statistiques d'utilisation avec le cache | |
class RateLimiter: | |
def __init__(self, requests_per_second: int = 10, burst_limit: int = 20): # 10 requetes par seconde, 20 requetes en rafale | |
""" | |
Initialise le rate limiter | |
requests_per_second: Nombre maximal de requêtes par seconde | |
burst_limit: Nombre maximal de requêtes en rafale | |
""" | |
self.rate = requests_per_second # Nombre maximal de requetes pas seconde (aujourd'hui) | |
self.burst_limit = burst_limit # Nombre maximal de requetes en rafale (aujourd'hui) | |
self.tokens = burst_limit # Nombre de jetons restants en rafale (aujourd'hui) | |
self.last_update = time.monotonic() # Derniere mise à jour | |
self.lock = asyncio.Lock() # Prise en charge du verrouillage | |
self._requests_count: Dict[str, int] = {} # Compteur de requetes | |
self.last_cleanup = time.time() # Date du dernier nettoyage | |
self.cleanup_interval = 3600 # 1 heure en secondes pour le nettoyage au bout d'une heure | |
self.cache_manager = CacheManager() # utilisation du cache pour stocker les statisqtiques d'utilisation (aujourd'hui) | |
async def acquire(self, user_id: Optional[str] = None) -> bool: | |
"""Tente d'acquérir un token pour faire une requête""" | |
async with self.lock: | |
now = time.monotonic() | |
time_passed = now - self.last_update | |
self.tokens = min( | |
self.burst_limit, | |
self.tokens + time_passed * self.rate | |
) | |
self.last_update = now | |
if self.tokens >= 1: | |
self.tokens -= 1 | |
if user_id: | |
await self._track_request(user_id) | |
return True | |
log_to_file(f"Rate limit atteint pour l'utilisateur {user_id}", level=logging.WARNING) | |
return False | |
async def _get_stats_from_cache(self, user_id: str) -> Optional[Dict]: | |
"""Récupère les statistiques depuis le cache""" | |
cache_key = f"usage_stats:{user_id}" | |
return await cache_manager.get(cache_key) | |
async def _update_stats_cache(self, user_id: str, stats: Dict): | |
"""Met à jour les statistiques dans le cache""" | |
cache_key = f"usage_stats:{user_id}" | |
await cache_manager.set(cache_key, stats, expiry=timedelta(hours=1)) | |
async def _track_request(self, user_id: str) -> None: | |
"""Suit les requêtes par utilisateur avec cache""" | |
try: | |
current_time = datetime.now(pytz.UTC).isoformat() | |
stats_data = None # Initialiser stats_data | |
# Vérifier d'abord le cache | |
cached_stats = await self._get_stats_from_cache(user_id) | |
if cached_stats: | |
# Mettre à jour les stats en cache | |
cached_stats['request_count'] += 1 | |
cached_stats['last_request'] = current_time | |
await self._update_stats_cache(user_id, cached_stats) | |
stats_data = cached_stats | |
# Mettre à jour Supabase de manière asynchrone | |
supabase.table("usage_statistics").update({ | |
"request_count": cached_stats['request_count'], | |
"last_request": current_time | |
}).eq("user_id", user_id).execute() | |
else: | |
# Vérifier Supabase | |
existing = supabase.table("usage_statistics").select("*").eq("user_id", user_id).execute() | |
if existing.data: | |
# Mettre à jour les stats existantes | |
stats = existing.data[0] | |
new_count = stats.get('request_count', 0) + 1 | |
stats_data = { | |
"request_count": new_count, | |
"last_request": current_time | |
} | |
supabase.table("usage_statistics").update(stats_data).eq("user_id", user_id).execute() | |
await self._update_stats_cache(user_id, stats_data) | |
else: | |
# Créer nouvelles stats | |
stats_data = { | |
"user_id": user_id, | |
"request_count": 1, | |
"last_request": current_time, | |
"last_cleanup": current_time | |
} | |
supabase.table("usage_statistics").insert(stats_data).execute() | |
await self._update_stats_cache(user_id, stats_data) | |
if stats_data: # Vérifier que stats_data existe | |
self._requests_count[user_id] = stats_data['request_count'] | |
log_to_file(f"Nouvelle requête pour l'utilisateur {user_id}. Total: {stats_data['request_count']}", level=logging.INFO) | |
except Exception as e: | |
log_to_file(f"Erreur lors du suivi des requêtes: {str(e)}", level=logging.ERROR) | |
# Continuer avec le comptage en mémoire en cas d'erreur | |
if user_id not in self._requests_count: | |
self._requests_count[user_id] = 1 | |
else: | |
self._requests_count[user_id] += 1 | |
async def get_user_requests_count(self, user_id: str) -> int: | |
"""Retourne le nombre de requêtes avec cache""" | |
try: | |
cached_stats = await self._get_stats_from_cache(user_id) | |
if cached_stats: | |
return cached_stats.get('request_count', 0) | |
response = supabase.table("usage_statistics").select("request_count").eq("user_id", user_id).execute() | |
if response.data: | |
count = response.data[0].get('request_count', 0) | |
await self._update_stats_cache(user_id, {"request_count": count}) | |
return count | |
return 0 | |
except Exception as e: | |
log_to_file(f"Erreur lors de la récupération du compte des requêtes: {str(e)}", level=logging.ERROR) | |
return self._requests_count.get(user_id, 0) |