diff --git a/docs/api/steam.util.rst b/docs/api/steam.util.rst index 3e24e1f..46e73b2 100644 --- a/docs/api/steam.util.rst +++ b/docs/api/steam.util.rst @@ -6,8 +6,16 @@ util :undoc-members: :show-inheritance: +util.throttle +------------- + +.. automodule:: steam.util.throttle + :members: + :undoc-members: + :show-inheritance: + util.web ------------ +-------- .. automodule:: steam.util.web :members: diff --git a/steam/util/throttle.py b/steam/util/throttle.py new file mode 100644 index 0000000..b11e8c5 --- /dev/null +++ b/steam/util/throttle.py @@ -0,0 +1,63 @@ +import sys +import time +import gevent + +if sys.version_info >= (3,3): + _monotonic = time.monotonic +else: + _monotonic = time.time # not really monotonic vOv + + +class ConstantRateLimit(object): + def __init__(self, times, seconds, exit_wait=False, use_gevent=False): + """Context manager for enforcing constant rate on code inside the block . + + `rate = seconds / times` + + :param times: times to execute per... + :type times: :class:`int` + :param seconds: ...seconds + :type seconds: :class:`int` + :param exit_wait: whether to automatically call :meth:`wait` before exiting the block + :type exit_wait: :class:`bool` + :param use_gevent: whether to use `gevent.sleep()` instead of `time.sleep()` + :type use_gevent: :class:`bool` + + Example: + + .. code:: python + + with RateLimiter(1, 5) as r: + # code taking 1s + r.wait() # blocks for 4s + # code taking 7s + r.wait() # doesn't block + # code taking 1s + r.wait() # blocks for 4s + """ + self.__dict__.update(locals()) + self.rate = float(seconds) / times + + def __enter__(self): + self._update_ref() + return self + + def __exit__(self, etype, evalue, traceback): + if self.exit_wait: + self.wait() + + def _update_ref(self): + self._ref = _monotonic() + self.rate + + def wait(self): + """Blocks until the rate is met""" + now = _monotonic() + if now < self._ref: + delay = max(0, self._ref - now) + if self.use_gevent: + gevent.sleep(delay) + else: + time.sleep(delay) + self._update_ref() + +