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.7
- 3.2
- 3.3
- 3.4
- pypy
script: python setup.py test
script: python setup.py test

View File

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

View File

@ -2,6 +2,13 @@
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)
++++++++++++++++++

View File

@ -69,9 +69,12 @@ else:
# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
MAX_WAIT = 1073741823
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
if len(dargs) == 1 and callable(dargs[0]):
@ -105,7 +108,9 @@ class Retrying(object):
wait_exponential_multiplier=None, wait_exponential_max=None,
retry_on_exception=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_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:
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)
else:
@ -147,7 +155,10 @@ class Retrying(object):
if wait_exponential_multiplier is not None or wait_exponential_max is not None:
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)
else:
@ -237,13 +248,18 @@ class Retrying(object):
delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
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:
sleep = self.wait(attempt_number, delay_since_first_attempt_ms)
time.sleep(sleep / 1000.0)
attempt_number += 1
class Attempt(object):
"""
An Attempt encapsulates a call to a target function that may end as a
@ -276,6 +292,7 @@ class Attempt(object):
else:
return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value)
class RetryError(Exception):
"""
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.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet',
'Topic :: Utilities',
]
settings.update(
name='retrying',
version='1.2.1',
version='1.2.3',
description='Retrying',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),

View File

@ -40,6 +40,13 @@ class TestStopConditions(unittest.TestCase):
def test_legacy_explicit_stop_type(self):
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):
def test_no_sleep(self):
@ -114,6 +121,13 @@ class TestWaitConditions(unittest.TestCase):
def test_legacy_explicit_wait_type(self):
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:
"""
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(result)
def test_with_stop(self):
def test_with_stop_on_return_value(self):
try:
_retryable_test_with_stop(NoneReturnUntilAfterCount(5))
self.fail("Expected RetryError after 3 attempts")
@ -292,6 +306,14 @@ class TestDecoratorWrapper(unittest.TestCase):
self.assertTrue(re.last_attempt.value is None)
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):
self.assertTrue(_retryable_test_with_exception_type_io(NoIOErrorAfterCount(5)))
@ -303,7 +325,7 @@ class TestDecoratorWrapper(unittest.TestCase):
print(n)
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")
except RetryError as re:
self.assertEqual(3, re.last_attempt.attempt_number)
@ -323,7 +345,7 @@ class TestDecoratorWrapper(unittest.TestCase):
print(n)
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")
except RetryError as re:
self.assertEqual(3, re.last_attempt.attempt_number)