Merge tag '1.2.3' into debian/unstable
Release 1.2.3
This commit is contained in:
commit
ed49cbbb70
@ -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
|
||||
|
@ -15,3 +15,7 @@ Patches and Suggestions
|
||||
- Justin Turner Arthur
|
||||
- J Derek Wilson
|
||||
- Alex Kuang
|
||||
- Simon Dollé
|
||||
- Rees Dooley
|
||||
- Saul Shanabrook
|
||||
- Daniel Nephin
|
||||
|
@ -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)
|
||||
++++++++++++++++++
|
||||
|
27
retrying.py
27
retrying.py
@ -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.
|
||||
|
4
setup.py
4
setup.py
@ -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(),
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user