diff options
author | Ray Holder <ray.holder+github@gmail.com> | 2014-05-05 03:15:21 +0200 |
---|---|---|
committer | Ray Holder <ray.holder+github@gmail.com> | 2014-05-05 03:15:21 +0200 |
commit | 389d407f9d8faf55917627b77037b003f3a8b4d0 (patch) | |
tree | 9b0e182098cf8d3cbcaaabd49bacaeaee6220f65 | |
parent | 6bc111fd90786a8f137b4b1d44a76bd9e9b4d465 (diff) | |
parent | 5271e7790e9e2bbe52ea03a61c6e1f5d4fc751b1 (diff) | |
download | retrying-389d407f9d8faf55917627b77037b003f3a8b4d0.tar.gz |
Merge pull request #3 from underrun/master
remove need for specification of stop/wait type
-rw-r--r-- | AUTHORS.rst | 1 | ||||
-rw-r--r-- | README.rst | 10 | ||||
-rw-r--r-- | retrying.py | 97 | ||||
-rw-r--r-- | test_retrying.py | 32 |
4 files changed, 82 insertions, 58 deletions
diff --git a/AUTHORS.rst b/AUTHORS.rst index 7ff47d1..ec69c5d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -13,3 +13,4 @@ Patches and Suggestions - Anthony McClosky - Jason Dunkelberger - Justin Turner Arthur +- J Derek Wilson @@ -75,7 +75,7 @@ Let's be a little less persistent and set some boundaries, such as the number of .. code-block:: python - @retry(stop='stop_after_attempt', stop_max_attempt_number=7) + @retry(stop_max_attempt_number=7) def stop_after_7_attempts(): print "Stopping after 7 attempts" @@ -83,7 +83,7 @@ We don't have all day, so let's set a boundary for how long we should be retryin .. code-block:: python - @retry(stop='stop_after_delay', stop_max_delay=10000) + @retry(stop_max_delay=10000) def stop_after_10_s(): print "Stopping after 10 seconds" @@ -91,7 +91,7 @@ Most things don't like to be polled as fast as possible, so let's just wait 2 se .. code-block:: python - @retry(wait='fixed_sleep', wait_fixed=2000) + @retry(wait_fixed=2000) def wait_2_s(): print "Wait 2 second between retries" @@ -100,7 +100,7 @@ Some things perform best with a bit of randomness injected. .. code-block:: python - @retry(wait='random_sleep', wait_random_min=1000, wait_random_max=2000) + @retry(wait_random_min=1000, wait_random_max=2000) def wait_random_1_to_2_s(): print "Randomly wait 1 to 2 seconds between retries" @@ -108,7 +108,7 @@ Then again, it's hard to beat exponential backoff when retrying distributed serv .. code-block:: python - @retry(wait='exponential_sleep', wait_exponential_multiplier=1000, wait_exponential_max=10000) + @retry(wait_exponential_multiplier=1000, wait_exponential_max=10000) def wait_exponential_1000(): print "Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards" diff --git a/retrying.py b/retrying.py index 6deddd3..d705d14 100644 --- a/retrying.py +++ b/retrying.py @@ -73,54 +73,85 @@ def retry(*dargs, **dkw): """ TODO comment """ - def wrap(f): - def wrapped_f(*args, **kw): - return Retrying(*dargs, **dkw).call(f, *args, **kw) - return wrapped_f - - def wrap_simple(f): - def wrapped_f(*args, **kw): - return Retrying().call(f, *args, **kw) - return wrapped_f - # support both @retry and @retry() as valid syntax if len(dargs) == 1 and callable(dargs[0]): + def wrap_simple(f): + def wrapped_f(*args, **kw): + return Retrying().call(f, *args, **kw) + + return wrapped_f + return wrap_simple(dargs[0]) + else: + def wrap(f): + def wrapped_f(*args, **kw): + return Retrying(*dargs, **dkw).call(f, *args, **kw) + + return wrapped_f + return wrap -class Retrying: +class Retrying(object): def __init__(self, - stop='never_stop', - stop_max_attempt_number=5, - stop_max_delay=100, - wait='no_sleep', - wait_fixed=1000, - wait_random_min=0, wait_random_max=1000, - wait_incrementing_start=0, wait_incrementing_increment=100, - wait_exponential_multiplier=1, wait_exponential_max=MAX_WAIT, + stop=None, wait=None, + stop_max_attempt_number=None, + stop_max_delay=None, + wait_fixed=None, + wait_random_min=None, wait_random_max=None, + wait_incrementing_start=None, wait_incrementing_increment=None, + wait_exponential_multiplier=None, wait_exponential_max=None, retry_on_exception=None, retry_on_result=None, wrap_exception=False): + self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number + self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay + self._wait_fixed = 1000 if wait_fixed is None else wait_fixed + self._wait_random_min = 0 if wait_random_min is None else wait_random_min + self._wait_random_max = 1000 if wait_random_max is None else wait_random_max + self._wait_incrementing_start = 0 if wait_incrementing_start is None else wait_incrementing_start + self._wait_incrementing_increment = 100 if wait_incrementing_increment is None else wait_incrementing_increment + self._wait_exponential_multiplier = 1 if wait_exponential_multiplier is None else wait_exponential_multiplier + self._wait_exponential_max = MAX_WAIT if wait_exponential_max is None else wait_exponential_max + # TODO add chaining of stop behaviors # stop behavior - self.stop = getattr(self, stop) - self._stop_max_attempt_number = stop_max_attempt_number - self._stop_max_delay = stop_max_delay + stop_funcs = [] + if stop_max_attempt_number is not None: + stop_funcs.append(self.stop_after_attempt) + + if stop_max_delay is not None: + stop_funcs.append(self.stop_after_delay) + + if stop is None: + self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs) + + else: + self.stop = getattr(self, stop) # TODO add chaining of wait behaviors # wait behavior - self.wait = getattr(self, wait) - self._wait_fixed = wait_fixed - self._wait_random_min = wait_random_min - self._wait_random_max = wait_random_max - self._wait_incrementing_start = wait_incrementing_start - self._wait_incrementing_increment = wait_incrementing_increment - self._wait_exponential_multiplier = wait_exponential_multiplier - self._wait_exponential_max = wait_exponential_max + wait_funcs = [lambda *args, **kwargs: 0] + if wait_fixed is not None: + wait_funcs.append(self.fixed_sleep) + + if wait_random_min is not None or wait_random_max is not None: + wait_funcs.append(self.random_sleep) + + if wait_incrementing_start is not None or wait_incrementing_increment is not None: + wait_funcs.append(self.incrementing_sleep) + + if wait_exponential_multiplier is not None or wait_exponential_max is not None: + wait_funcs.append(self.exponential_sleep) + + if wait is None: + self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs) + + else: + self.wait = self.getattr(wait) # retry on exception filter if retry_on_exception is None: @@ -137,10 +168,6 @@ class Retrying: self._wrap_exception = wrap_exception - def never_stop(self, previous_attempt_number, delay_since_first_attempt_ms): - """Never stop retrying.""" - return False - def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms): """Stop after the previous attempt >= stop_max_attempt_number.""" return previous_attempt_number >= self._stop_max_attempt_number @@ -217,7 +244,7 @@ class Retrying: attempt_number += 1 -class Attempt: +class Attempt(object): """ An Attempt encapsulates a call to a target function that may end as a normal return value from the function or an Exception depending on what diff --git a/test_retrying.py b/test_retrying.py index 987b65c..a1aac6f 100644 --- a/test_retrying.py +++ b/test_retrying.py @@ -22,17 +22,17 @@ from retrying import retry class TestStopConditions(unittest.TestCase): def test_never_stop(self): - r = Retrying(stop='never_stop') + r = Retrying() self.assertFalse(r.stop(3, 6546)) def test_stop_after_attempt(self): - r = Retrying(stop='stop_after_attempt', stop_max_attempt_number=3) + r = Retrying(stop_max_attempt_number=3) self.assertFalse(r.stop(2, 6546)) self.assertTrue(r.stop(3, 6546)) self.assertTrue(r.stop(4, 6546)) def test_stop_after_delay(self): - r = Retrying(stop='stop_after_delay', stop_max_delay=1000) + r = Retrying(stop_max_delay=1000) self.assertFalse(r.stop(2, 999)) self.assertTrue(r.stop(2, 1000)) self.assertTrue(r.stop(2, 1001)) @@ -40,21 +40,21 @@ class TestStopConditions(unittest.TestCase): class TestWaitConditions(unittest.TestCase): def test_no_sleep(self): - r = Retrying(wait='no_sleep') + r = Retrying() self.assertEqual(0, r.wait(18, 9879)) def test_fixed_sleep(self): - r = Retrying(wait='fixed_sleep', wait_fixed=1000) + r = Retrying(wait_fixed=1000) self.assertEqual(1000, r.wait(12, 6546)) def test_incrementing_sleep(self): - r = Retrying(wait='incrementing_sleep', wait_incrementing_start=500, wait_incrementing_increment=100) + r = Retrying(wait_incrementing_start=500, wait_incrementing_increment=100) self.assertEqual(500, r.wait(1, 6546)) self.assertEqual(600, r.wait(2, 6546)) self.assertEqual(700, r.wait(3, 6546)) def test_random_sleep(self): - r = Retrying(wait='random_sleep', wait_random_min=1000, wait_random_max=2000) + r = Retrying(wait_random_min=1000, wait_random_max=2000) times = set() times.add(r.wait(1, 6546)) times.add(r.wait(1, 6546)) @@ -66,7 +66,7 @@ class TestWaitConditions(unittest.TestCase): self.assertTrue(t <= 2000) def test_random_sleep_without_min(self): - r = Retrying(wait='random_sleep', wait_random_max=2000) + r = Retrying(wait_random_max=2000) times = set() times.add(r.wait(1, 6546)) times.add(r.wait(1, 6546)) @@ -78,7 +78,7 @@ class TestWaitConditions(unittest.TestCase): self.assertTrue(t <= 2000) def test_exponential(self): - r = Retrying(wait='exponential_sleep') + r = Retrying(wait_exponential_max=100000) self.assertEqual(r.wait(1, 0), 2) self.assertEqual(r.wait(2, 0), 4) self.assertEqual(r.wait(3, 0), 8) @@ -87,7 +87,7 @@ class TestWaitConditions(unittest.TestCase): self.assertEqual(r.wait(6, 0), 64) def test_exponential_with_max_wait(self): - r = Retrying(wait='exponential_sleep', wait_exponential_max=40) + r = Retrying(wait_exponential_max=40) self.assertEqual(r.wait(1, 0), 2) self.assertEqual(r.wait(2, 0), 4) self.assertEqual(r.wait(3, 0), 8) @@ -98,7 +98,7 @@ class TestWaitConditions(unittest.TestCase): self.assertEqual(r.wait(50, 0), 40) def test_exponential_with_max_wait_and_multiplier(self): - r = Retrying(wait='exponential_sleep', wait_exponential_max=50000, wait_exponential_multiplier=1000) + r = Retrying(wait_exponential_max=50000, wait_exponential_multiplier=1000) self.assertEqual(r.wait(1, 0), 2000) self.assertEqual(r.wait(2, 0), 4000) self.assertEqual(r.wait(3, 0), 8000) @@ -208,11 +208,11 @@ def retry_if_exception_of_type(retryable_types): def current_time_ms(): return int(round(time.time() * 1000)) -@retry(wait='fixed_sleep', wait_fixed=50, retry_on_result=retry_if_result_none) +@retry(wait_fixed=50, retry_on_result=retry_if_result_none) def _retryable_test_with_wait(thing): return thing.go() -@retry(stop='stop_after_attempt', stop_max_attempt_number=3, retry_on_result=retry_if_result_none) +@retry(stop_max_attempt_number=3, retry_on_result=retry_if_result_none) def _retryable_test_with_stop(thing): return thing.go() @@ -225,14 +225,12 @@ def _retryable_test_with_exception_type_io_wrap(thing): return thing.go() @retry( - stop='stop_after_attempt', stop_max_attempt_number=3, retry_on_exception=retry_if_exception_of_type(IOError)) def _retryable_test_with_exception_type_io_attempt_limit(thing): return thing.go() @retry( - stop='stop_after_attempt', stop_max_attempt_number=3, retry_on_exception=retry_if_exception_of_type(IOError), wrap_exception=True) @@ -256,14 +254,12 @@ def _retryable_test_with_exception_type_custom_wrap(thing): return thing.go() @retry( - stop='stop_after_attempt', stop_max_attempt_number=3, retry_on_exception=retry_if_exception_of_type(CustomError)) def _retryable_test_with_exception_type_custom_attempt_limit(thing): return thing.go() @retry( - stop='stop_after_attempt', stop_max_attempt_number=3, retry_on_exception=retry_if_exception_of_type(CustomError), wrap_exception=True) @@ -383,4 +379,4 @@ class TestDecoratorWrapper(unittest.TestCase): self.assertTrue(_retryable_default_f(NoCustomErrorAfterCount(5))) if __name__ == '__main__': - unittest.main()
\ No newline at end of file + unittest.main() |