summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRay Holder <ray.holder+github@gmail.com>2014-05-05 03:15:21 +0200
committerRay Holder <ray.holder+github@gmail.com>2014-05-05 03:15:21 +0200
commit389d407f9d8faf55917627b77037b003f3a8b4d0 (patch)
tree9b0e182098cf8d3cbcaaabd49bacaeaee6220f65
parent6bc111fd90786a8f137b4b1d44a76bd9e9b4d465 (diff)
parent5271e7790e9e2bbe52ea03a61c6e1f5d4fc751b1 (diff)
downloadretrying-389d407f9d8faf55917627b77037b003f3a8b4d0.tar.gz
Merge pull request #3 from underrun/master
remove need for specification of stop/wait type
-rw-r--r--AUTHORS.rst1
-rw-r--r--README.rst10
-rw-r--r--retrying.py97
-rw-r--r--test_retrying.py32
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
diff --git a/README.rst b/README.rst
index 3d224b2..1adb67e 100644
--- a/README.rst
+++ b/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"
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()