From cd926d6909ff3992bcec127058b913a304c52c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl> Date: Tue, 23 Feb 2021 16:52:52 +0100 Subject: [PATCH] add ExponentialBackoff --- CHANGELOG.md | 2 ++ docs/time.rst | 6 ++++++ satella/__init__.py | 2 +- satella/time.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- tests/test_time.py | 11 ++++++++++- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55bf2089..2219cfd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ # v2.14.44 + +* added `ExponentialBackoff` diff --git a/docs/time.rst b/docs/time.rst index efec99eb..17613b96 100644 --- a/docs/time.rst +++ b/docs/time.rst @@ -35,3 +35,9 @@ sleep ----- .. autofunction:: satella.time.sleep + +ExponentialBackoff +------------------ + +.. autoclass:: satella.time.ExponentialBackoff + :members: diff --git a/satella/__init__.py b/satella/__init__.py index e1cb8af3..5f99ec58 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.14.44a1' +__version__ = '2.14.44' diff --git a/satella/time.py b/satella/time.py index 5fa1683f..6df47795 100644 --- a/satella/time.py +++ b/satella/time.py @@ -7,7 +7,7 @@ import warnings from concurrent.futures import Future from functools import wraps # import from functools to prevent circular import exception -__all__ = ['measure', 'time_as_int', 'time_ms', 'sleep', 'time_us'] +__all__ = ['measure', 'time_as_int', 'time_ms', 'sleep', 'time_us', 'ExponentialBackoff'] from satella.exceptions import WouldWaitMore @@ -321,3 +321,46 @@ class measure: if self.stop_on_stop: self.stop() return False + + +class ExponentialBackoff: + """ + A class that will sleep increasingly longer on errors. Meant to be used in such a way: + + >>> eb = ExponentialBackoff(start=2, limit=30) + >>> while not connect(): + >>> eb.failed() + >>> eb.sleep() + >>> eb.success() + + :param start: value at which to start + :param limit: maximum sleep timeout + :param sleep_fun: function used to sleep. Will accept a single argument - number of + seconds to wait + """ + __slots__ = ('start', 'limit', 'counter', 'sleep_fun') + + def __init__(self, start: float = 1, limit: float = 30, + sleep_fun: tp.Callable[[float], None] = sleep): + self.start = start + self.limit = limit + self.counter = start + self.sleep_fun = sleep_fun + + def sleep(self): + """ + Called when sleep is expected. + """ + self.sleep_fun(self.counter) + + def failed(self): + """ + Called when something fails. + """ + self.counter = min(self.limit, self.counter * 2) + + def success(self): + """ + Called when something successes. + """ + self.counter = self.start diff --git a/tests/test_time.py b/tests/test_time.py index cc35dcbb..0699f8f0 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -4,12 +4,21 @@ import time import multiprocessing import os import sys -from satella.time import measure, time_as_int, time_ms, sleep +from satella.time import measure, time_as_int, time_ms, sleep, ExponentialBackoff from concurrent.futures import Future class TestTime(unittest.TestCase): + def test_exponential_backoff(self): + with measure() as measurement: + eb = ExponentialBackoff() + eb.failed() + eb.sleep() + eb.failed() + eb.sleep() + self.assertGreaterEqual(measurement(), 2+4) + def test_measure(self): with measure(timeout=0.5) as measurement: self.assertFalse(measurement.timeouted) -- GitLab