diff --git a/CHANGELOG.md b/CHANGELOG.md index b7125219fbd6ad109237a3ac0d21b4905a953228..caa704b081a7fb8bf635d02b49ebf61d13ef604d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,4 @@ # v2.17.18 + +* added parameter immediate to ExponentialBackoff.launch +* fixed a bug with ExponentialBackoff.launch diff --git a/satella/__init__.py b/satella/__init__.py index 487940e3aacd669ff41b24c148cd31905e6d550b..f5ef4834275e1683451bee8a007090b01e487655 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.17.18a1' +__version__ = '2.17.18' diff --git a/satella/time/backoff.py b/satella/time/backoff.py index 68c3583b51c9b7c98027de83622a1aac254e56b1..ec54ee92c3575a186771f47d7e4e6a2ff045996e 100644 --- a/satella/time/backoff.py +++ b/satella/time/backoff.py @@ -129,7 +129,8 @@ class ExponentialBackoff: self.unavailable_until = None self.condition.notify_all() - def launch(self, exceptions_on_failed: ExceptionList = Exception): + def launch(self, exceptions_on_failed: ExceptionList = Exception, + immediate: bool = False): """ A decorator to simplify writing doing-something loops. Basically, this: @@ -149,19 +150,29 @@ class ExponentialBackoff: >>> eb.failed() >>> eb.sleep() + The first example with :code:`immediate=True` could skip the last call to do_action, + as it will be executed automatically with zero parameters if immediate=True is set. + :param exceptions_on_failed: a list of a single exception of exceptions whose raising will signal that fun has failed + :param immediate: immediately execute the function, but return the wrapped function + as a result of this decorator. The function will be called with zero arguments. :return: a function, that called, will pass the exactly same parameters """ def outer(fun): @wraps(fun) def inner(*args, **kwargs): - try: - r = fun(*args, **kwargs) - self.success() - return r - except exceptions_on_failed: - self.failed() - self.sleep() + while True: + try: + r = fun(*args, **kwargs) + self.success() + return r + except exceptions_on_failed: + self.failed() + self.sleep() + if immediate: + inner() + return inner + return outer diff --git a/tests/test_time.py b/tests/test_time.py index b33fb42f26a5ef0ac5795629c20f24cddc5d0896..a862aeccea9fecc31dfd5366007c38d6d6f4bb4c 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -38,13 +38,32 @@ class TestTime(unittest.TestCase): time.sleep(1) eb.success() + def test_exponential_backoff_launch_immediate(self): + eb = ExponentialBackoff(start=2, limit=30) + i = 1 + a = 0 + with measure() as m: + @eb.launch(ValueError, immediate=True) + def do_action(): + nonlocal i, a + a += 1 + if i == 4: + return + else: + i += 1 + raise ValueError() + self.assertGreaterEqual(m(), 2) + self.assertGreaterEqual(a, 2) + def test_exponential_backoff_launch(self): eb = ExponentialBackoff(start=2, limit=30) i = 1 + a = 0 @eb.launch(ValueError) def do_action(): - nonlocal i - if i == 3: + nonlocal i, a + a += 1 + if i == 4: return else: i += 1 @@ -53,6 +72,7 @@ class TestTime(unittest.TestCase): with measure() as m: do_action() self.assertGreaterEqual(m(), 2) + self.assertGreaterEqual(a, 2) def test_exponential_backoff_waiting_for_service_healthy(self): eb = ExponentialBackoff(start=2, limit=30)