Skip to content

Instantly share code, notes, and snippets.

@fbparis
Last active November 26, 2018 12:16
Show Gist options
  • Save fbparis/69170242a88085e249feff8083b0c440 to your computer and use it in GitHub Desktop.
Save fbparis/69170242a88085e249feff8083b0c440 to your computer and use it in GitHub Desktop.
Generic decorator to handle multiple rate limits in API calls
import sys, time
from collections import deque
_DEFAULT_POLICY = {1:1}
def rate_limited(*args):
"""
policy is an object where each key is a time interval in seconds and each value is a maximum number of requests during this interval.
{1:1} means 1 request max per second
{1:5} means 5 requests max per second
{1:2,60:60,3600:1800} means 1800 requests max per hour, 60 requests max per minute, 2 requests max per second
"""
def decorator(f):
def wrapper(*args, **kwargs):
if len(RATE_LIMITS) > 0:
now = time.time()
N = len(RATE_LIMITS)
timestamps = [now - x[0] for x in RATE_LIMITS]
indexes = [None] * N
counts = [0] * N
while len(TIMESTAMPS):
if TIMESTAMPS[0] < timestamps[0]:
TIMESTAMPS.popleft()
else:
break
for n, t in enumerate(TIMESTAMPS):
for i in range(N):
if t >= timestamps[i]:
if indexes[i] is None:
indexes[i] = n
counts[i] += 1
else:
break
delay = 0.
for i in range(N):
if indexes[i] is None:
break
if counts[i] >= RATE_LIMITS[i][1]:
delay = max(delay, TIMESTAMPS[indexes[i] + counts[i] - RATE_LIMITS[i][1]] - timestamps[i])
if delay > 0:
sys.stderr.write("%s waiting %.1fs\n" % (f, delay))
time.sleep(delay)
TIMESTAMPS.append(time.time())
return f(*args, **kwargs)
return wrapper
TIMESTAMPS = deque([])
if len(args) == 1 and callable(args[0]):
policy = __DEFAULT_POLICY
_decorator = decorator(args[0])
else:
if len(args):
policy = args[0]
else:
policy = __DEFAULT_POLICY
_decorator = decorator
RATE_LIMITS = sorted([(a, b) for (a, b) in policy.items() if min(a, b) > 0], reverse=True)
return _decorator
if __name__ == '__main__':
from threading import Thread
@rate_limited({1:3, 5:7, 10:11})
def f(n):
return "f(%d) done" % n
@rate_limited({1:5, 2:7, 3:11})
def g(n):
return "g(%d) done" % n
def run(f):
for i in range(20):
print(f(i))
t1 = Thread(target=run, args=(f,))
t2 = Thread(target=run, args=(g,))
t1.start()
t2.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment