Spaces:
Running on Zero
Running on Zero
File size: 1,943 Bytes
bfd6783 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | """Per-user throttling for model-backed work.
Only relevant in open-access mode. Reading a receipt or a statement spends the
Space owner's inference credits, so without a cap a single stranger with a
folder of PDFs can run up an unbounded bill on someone else's token.
Deliberately in-memory: it resets when the Space restarts or sleeps, which is
fine for a budget guard and avoids putting other people's usage records in the
owner's dataset. Plain text costs nothing (the deterministic parser handles it)
and is never throttled.
"""
from __future__ import annotations
import threading
import time
from collections import defaultdict
WINDOW_SECONDS = 24 * 60 * 60
class RateLimiter:
def __init__(self, per_day: int):
#: Zero or negative disables the limiter entirely.
self.per_day = per_day
self._hits: dict[int, list[float]] = defaultdict(list)
self._lock = threading.Lock()
@property
def enabled(self) -> bool:
return self.per_day > 0
def _prune(self, user_id: int, now: float) -> list[float]:
recent = [t for t in self._hits[user_id] if now - t < WINDOW_SECONDS]
self._hits[user_id] = recent
return recent
def check(self, user_id: int, now: float | None = None) -> bool:
"""Record an attempt. False means the user is over their daily cap."""
if not self.enabled:
return True
now = now if now is not None else time.time()
with self._lock:
recent = self._prune(user_id, now)
if len(recent) >= self.per_day:
return False
recent.append(now)
return True
def remaining(self, user_id: int, now: float | None = None) -> int:
if not self.enabled:
return -1 # unlimited
now = now if now is not None else time.time()
with self._lock:
return max(0, self.per_day - len(self._prune(user_id, now)))
|