Oracle / finbot /limits.py
spacedout-bits's picture
Deploy coco-finbot
bfd6783 verified
Raw History Blame Contribute Delete
1.94 kB
"""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)))