From 3dff144b91e20ff1f6e9ccc2fd7694a894cabe41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Tue, 31 Jul 2018 11:48:41 +0200 Subject: [PATCH] Fix RateLimiter family debounce stuff --- lib/tfw/decorators/rate_limiter.py | 44 ++++++++++++++---------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/tfw/decorators/rate_limiter.py b/lib/tfw/decorators/rate_limiter.py index 0f7f20b..abe6453 100644 --- a/lib/tfw/decorators/rate_limiter.py +++ b/lib/tfw/decorators/rate_limiter.py @@ -21,31 +21,28 @@ class RateLimiter: so it is only acceptable for stuff running on a separate thread. If this is no good for you please refer to AsyncRateLimiter in this module, - which is designed not to block and use the IOLoop it is being called from, - or redefine the action argument of __init__ (which defaults to time.sleep). + which is designed not to block and use the IOLoop it is being called from. """ - def __init__(self, rate_per_second, action=sleep): + def __init__(self, rate_per_second): """ :param rate_per_second: max frequency the decorated method should be invoked with - :param action: what to do when rate limiting. defaults to time.sleep, - receives the number of seconds until the next invocation - should occour as an argument """ self.min_interval = 1 / float(rate_per_second) - self.action = action self.fun = None self.last_call = time() + def action(self, seconds_to_next_call): + if seconds_to_next_call: + sleep(seconds_to_next_call) + self.fun() + def __call__(self, fun): @wraps(fun) def wrapper(*args, **kwargs): self.fun = partial(fun, *args, **kwargs) limit_seconds = self._limit_rate() - if limit_seconds: - self.action(limit_seconds) - return - self.fun() + self.action(limit_seconds) return wrapper def _limit_rate(self): @@ -62,10 +59,11 @@ class AsyncRateLimiter(RateLimiter): """ Decorator class for rate limiting, non-blocking. - The semantics of the rate limiting are similar to that of RateLimiter, - but this decorator never blocks, instead it adds an async callback version - of the decorated function to the IOLoop to be executed after the rate limiting - has expired. + The semantics of the rate limiting: + - unlike RateLimiter this decorator never blocks, instead it adds an async + callback version of the decorated function to the IOLoop + (to be executed after the rate limiting has expired). + - the timing works similarly to RateLimiter """ def __init__(self, rate_per_second, ioloop_factory): """ @@ -79,24 +77,24 @@ class AsyncRateLimiter(RateLimiter): self._last_callback = None self._make_action_thread_safe() - - super().__init__( - rate_per_second=rate_per_second, - action=self.async_action - ) + super().__init__(rate_per_second=rate_per_second) def _make_action_thread_safe(self): - self.async_action = partial(self.ioloop.add_callback, self.async_action) + self.action = partial(self.ioloop.add_callback, self.action) @lazy_property def ioloop(self): return self._ioloop_factory() - def async_action(self, seconds_to_next_call): + def action(self, seconds_to_next_call): if self._last_callback: self.ioloop.remove_timeout(self._last_callback) self._last_callback = self.ioloop.call_later( seconds_to_next_call, - self.fun + self.fun_with_debounce ) + + def fun_with_debounce(self): + self.last_call = time() + self.fun()