1
+ from typing import Optional
2
+
1
3
import time
2
4
import functools
5
+ from threading import Event , Lock
3
6
4
7
5
- class RateLimiter ( object ) :
8
+ class RateLimiter :
6
9
"""
7
10
Naive rate limiter context manager with smooth output.
8
11
@@ -70,7 +73,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback):
70
73
return self .exit ()
71
74
72
75
73
- class RetryableIterator ( object ) :
76
+ class RetryableIterator :
74
77
"""
75
78
Iterator exposing a #.retry method that will make sure the next item
76
79
is the same as the current one.
@@ -99,7 +102,7 @@ def retry(self):
99
102
self .retried = True
100
103
101
104
102
- class RateLimitedIterator ( object ) :
105
+ class RateLimitedIterator :
103
106
"""
104
107
Handy iterator wrapper that will yield its items while respecting a given
105
108
rate limit and that will not sleep needlessly when the iterator is
@@ -143,7 +146,7 @@ def __iter__(self):
143
146
self .rate_limiter .exit ()
144
147
145
148
146
- class RateLimiterState ( object ) :
149
+ class RateLimiterState :
147
150
def __init__ (self , max_per_period : int , period : float = 1.0 ):
148
151
max_per_second = max_per_period / period
149
152
self .min_interval = 1.0 / max_per_second
@@ -163,6 +166,47 @@ def update(self):
163
166
self .last_entry = time .perf_counter ()
164
167
165
168
169
+ class ThreadsafeBurstyRateLimiterState :
170
+ def __init__ (self , max_per_period : int , period : float = 1.0 ):
171
+ self .max_per_period = max_per_period
172
+ self .period = period
173
+
174
+ self .current_burst = 0
175
+ self .time_of_next_burst : Optional [float ] = None
176
+
177
+ self .event = Event ()
178
+ self .lock = Lock ()
179
+
180
+ self .event .set ()
181
+
182
+ def wait_if_needed (self ):
183
+ self .event .wait ()
184
+ self .lock .acquire ()
185
+
186
+ if self .current_burst < self .max_per_period :
187
+ if self .time_of_next_burst is None :
188
+ self .time_of_next_burst = time .perf_counter () + self .period
189
+
190
+ self .current_burst += 1
191
+ self .lock .release ()
192
+ return
193
+
194
+ assert self .time_of_next_burst is not None
195
+
196
+ delta = time .perf_counter () - self .time_of_next_burst
197
+
198
+ self .time_of_next_burst = None
199
+ self .current_burst = 0
200
+
201
+ if delta > 0 :
202
+ self .event .clear ()
203
+ self .lock .release ()
204
+ time .sleep (delta )
205
+ self .event .set ()
206
+ else :
207
+ self .lock .release ()
208
+
209
+
166
210
def rate_limited (max_per_period , period = 1.0 ):
167
211
state = RateLimiterState (max_per_period , period )
168
212
0 commit comments