Merge tag '1.2.3' into debian/unstable

Release 1.2.3
This commit is contained in:
Thomas Goirand 2014-09-20 12:49:10 +08:00
commit ed49cbbb70
6 changed files with 64 additions and 10 deletions

View File

@ -4,6 +4,8 @@ python:
- 2.6 - 2.6
- 2.7 - 2.7
- 3.2 - 3.2
- 3.3
- 3.4
- pypy - pypy
script: python setup.py test script: python setup.py test

View File

@ -15,3 +15,7 @@ Patches and Suggestions
- Justin Turner Arthur - Justin Turner Arthur
- J Derek Wilson - J Derek Wilson
- Alex Kuang - Alex Kuang
- Simon Dollé
- Rees Dooley
- Saul Shanabrook
- Daniel Nephin

View File

@ -2,6 +2,13 @@
History History
------- -------
1.2.3 (2014-08-25)
++++++++++++++++++
- Add support for custom wait and stop functions
1.2.2 (2014-06-20)
++++++++++++++++++
- Bug fix to not raise a RetryError on failure when exceptions aren't being wrapped
1.2.1 (2014-05-05) 1.2.1 (2014-05-05)
++++++++++++++++++ ++++++++++++++++++

View File

@ -69,9 +69,12 @@ else:
# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint... # sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
MAX_WAIT = 1073741823 MAX_WAIT = 1073741823
def retry(*dargs, **dkw): def retry(*dargs, **dkw):
""" """
TODO comment Decorator function that instantiates the Retrying object
@param *dargs: positional arguments passed to Retrying object
@param **dkw: keyword arguments passed to the Retrying object
""" """
# support both @retry and @retry() as valid syntax # support both @retry and @retry() as valid syntax
if len(dargs) == 1 and callable(dargs[0]): if len(dargs) == 1 and callable(dargs[0]):
@ -105,7 +108,9 @@ class Retrying(object):
wait_exponential_multiplier=None, wait_exponential_max=None, wait_exponential_multiplier=None, wait_exponential_max=None,
retry_on_exception=None, retry_on_exception=None,
retry_on_result=None, retry_on_result=None,
wrap_exception=False): wrap_exception=False,
stop_func=None,
wait_func=None):
self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number 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._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay
@ -126,7 +131,10 @@ class Retrying(object):
if stop_max_delay is not None: if stop_max_delay is not None:
stop_funcs.append(self.stop_after_delay) stop_funcs.append(self.stop_after_delay)
if stop is None: if stop_func is not None:
self.stop = stop_func
elif stop is None:
self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs) self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs)
else: else:
@ -147,7 +155,10 @@ class Retrying(object):
if wait_exponential_multiplier is not None or wait_exponential_max is not None: if wait_exponential_multiplier is not None or wait_exponential_max is not None:
wait_funcs.append(self.exponential_sleep) wait_funcs.append(self.exponential_sleep)
if wait is None: if wait_func is not None:
self.wait = wait_func
elif wait is None:
self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs) self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs)
else: else:
@ -237,13 +248,18 @@ class Retrying(object):
delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
if self.stop(attempt_number, delay_since_first_attempt_ms): if self.stop(attempt_number, delay_since_first_attempt_ms):
raise RetryError(attempt) if not self._wrap_exception and attempt.has_exception:
# get() on an attempt with an exception should cause it to be raised, but raise just in case
raise attempt.get()
else:
raise RetryError(attempt)
else: else:
sleep = self.wait(attempt_number, delay_since_first_attempt_ms) sleep = self.wait(attempt_number, delay_since_first_attempt_ms)
time.sleep(sleep / 1000.0) time.sleep(sleep / 1000.0)
attempt_number += 1 attempt_number += 1
class Attempt(object): class Attempt(object):
""" """
An Attempt encapsulates a call to a target function that may end as a An Attempt encapsulates a call to a target function that may end as a
@ -276,6 +292,7 @@ class Attempt(object):
else: else:
return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value) return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value)
class RetryError(Exception): class RetryError(Exception):
""" """
A RetryError encapsulates the last Attempt instance right before giving up. A RetryError encapsulates the last Attempt instance right before giving up.

View File

@ -23,13 +23,15 @@ CLASSIFIERS = [
'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet', 'Topic :: Internet',
'Topic :: Utilities', 'Topic :: Utilities',
] ]
settings.update( settings.update(
name='retrying', name='retrying',
version='1.2.1', version='1.2.3',
description='Retrying', description='Retrying',
long_description=open('README.rst').read() + '\n\n' + long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(), open('HISTORY.rst').read(),

View File

@ -40,6 +40,13 @@ class TestStopConditions(unittest.TestCase):
def test_legacy_explicit_stop_type(self): def test_legacy_explicit_stop_type(self):
r = Retrying(stop="stop_after_attempt") r = Retrying(stop="stop_after_attempt")
def test_stop_func(self):
r = Retrying(stop_func=lambda attempt, delay: attempt == delay)
self.assertFalse(r.stop(1, 3))
self.assertFalse(r.stop(100, 99))
self.assertTrue(r.stop(101, 101))
class TestWaitConditions(unittest.TestCase): class TestWaitConditions(unittest.TestCase):
def test_no_sleep(self): def test_no_sleep(self):
@ -114,6 +121,13 @@ class TestWaitConditions(unittest.TestCase):
def test_legacy_explicit_wait_type(self): def test_legacy_explicit_wait_type(self):
r = Retrying(wait="exponential_sleep") r = Retrying(wait="exponential_sleep")
def test_wait_func(self):
r = Retrying(wait_func=lambda attempt, delay: attempt * delay)
self.assertEqual(r.wait(1, 5), 5)
self.assertEqual(r.wait(2, 11), 22)
self.assertEqual(r.wait(10, 100), 1000)
class NoneReturnUntilAfterCount: class NoneReturnUntilAfterCount:
""" """
This class holds counter state for invoking a method several times in a row. This class holds counter state for invoking a method several times in a row.
@ -282,7 +296,7 @@ class TestDecoratorWrapper(unittest.TestCase):
self.assertTrue(t >= 250) self.assertTrue(t >= 250)
self.assertTrue(result) self.assertTrue(result)
def test_with_stop(self): def test_with_stop_on_return_value(self):
try: try:
_retryable_test_with_stop(NoneReturnUntilAfterCount(5)) _retryable_test_with_stop(NoneReturnUntilAfterCount(5))
self.fail("Expected RetryError after 3 attempts") self.fail("Expected RetryError after 3 attempts")
@ -292,6 +306,14 @@ class TestDecoratorWrapper(unittest.TestCase):
self.assertTrue(re.last_attempt.value is None) self.assertTrue(re.last_attempt.value is None)
print(re) print(re)
def test_with_stop_on_exception(self):
try:
_retryable_test_with_stop(NoIOErrorAfterCount(5))
self.fail("Expected IOError")
except IOError as re:
self.assertTrue(isinstance(re, IOError))
print(re)
def test_retry_if_exception_of_type(self): def test_retry_if_exception_of_type(self):
self.assertTrue(_retryable_test_with_exception_type_io(NoIOErrorAfterCount(5))) self.assertTrue(_retryable_test_with_exception_type_io(NoIOErrorAfterCount(5)))
@ -303,7 +325,7 @@ class TestDecoratorWrapper(unittest.TestCase):
print(n) print(n)
try: try:
_retryable_test_with_exception_type_io_attempt_limit(NoIOErrorAfterCount(5)) _retryable_test_with_exception_type_io_attempt_limit_wrap(NoIOErrorAfterCount(5))
self.fail("Expected RetryError") self.fail("Expected RetryError")
except RetryError as re: except RetryError as re:
self.assertEqual(3, re.last_attempt.attempt_number) self.assertEqual(3, re.last_attempt.attempt_number)
@ -323,7 +345,7 @@ class TestDecoratorWrapper(unittest.TestCase):
print(n) print(n)
try: try:
_retryable_test_with_exception_type_custom_attempt_limit(NoCustomErrorAfterCount(5)) _retryable_test_with_exception_type_custom_attempt_limit_wrap(NoCustomErrorAfterCount(5))
self.fail("Expected RetryError") self.fail("Expected RetryError")
except RetryError as re: except RetryError as re:
self.assertEqual(3, re.last_attempt.attempt_number) self.assertEqual(3, re.last_attempt.attempt_number)