41 lines
1.1 KiB
Python
41 lines
1.1 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import time
|
||
|
|
from collections import deque
|
||
|
|
|
||
|
|
RATE_LIMIT_WINDOW_SECONDS = 60
|
||
|
|
MAX_REQUESTS_PER_WINDOW = 2000
|
||
|
|
|
||
|
|
|
||
|
|
class GraphRateLimiter:
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
max_requests: int = MAX_REQUESTS_PER_WINDOW,
|
||
|
|
window_seconds: int = RATE_LIMIT_WINDOW_SECONDS,
|
||
|
|
):
|
||
|
|
self._max_requests = max_requests
|
||
|
|
self._window_seconds = window_seconds
|
||
|
|
self._timestamps: deque[float] = deque()
|
||
|
|
self._remaining: int = max_requests
|
||
|
|
|
||
|
|
def can_proceed(self) -> bool:
|
||
|
|
self._cleanup()
|
||
|
|
return len(self._timestamps) < self._max_requests
|
||
|
|
|
||
|
|
def record(self) -> None:
|
||
|
|
self._timestamps.append(time.monotonic())
|
||
|
|
self._cleanup()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def remaining(self) -> int:
|
||
|
|
self._cleanup()
|
||
|
|
return max(0, self._max_requests - len(self._timestamps))
|
||
|
|
|
||
|
|
def _cleanup(self) -> None:
|
||
|
|
cutoff = time.monotonic() - self._window_seconds
|
||
|
|
while self._timestamps and self._timestamps[0] < cutoff:
|
||
|
|
self._timestamps.popleft()
|
||
|
|
|
||
|
|
def reset(self) -> None:
|
||
|
|
self._timestamps.clear()
|