Merge pull request #3 from underrun/master
remove need for specification of stop/wait type
This commit is contained in:
commit
389d407f9d
|
@ -13,3 +13,4 @@ Patches and Suggestions
|
|||
- Anthony McClosky
|
||||
- Jason Dunkelberger
|
||||
- Justin Turner Arthur
|
||||
- J Derek Wilson
|
||||
|
|
10
README.rst
10
README.rst
|
@ -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"
|
||||
|
||||
|
|
97
retrying.py
97
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
|
||||
|
|
|
@ -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()
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue