propagate complete tracebacks such that they show up in nested stack frames, adjust api and tests #1
This commit is contained in:
parent
f728447c8c
commit
f405d7ecc3
17
retrying.py
17
retrying.py
|
@ -15,6 +15,7 @@
|
|||
import random
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
|
||||
MAX_WAIT = 1073741823
|
||||
|
@ -139,7 +140,7 @@ class Retrying:
|
|||
def should_reject(self, attempt):
|
||||
reject = False
|
||||
if attempt.has_exception:
|
||||
reject |= self._retry_on_exception(attempt.value)
|
||||
reject |= self._retry_on_exception(attempt.value[1])
|
||||
else:
|
||||
reject |= self._retry_on_result(attempt.value)
|
||||
|
||||
|
@ -152,8 +153,8 @@ class Retrying:
|
|||
try:
|
||||
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
|
||||
except:
|
||||
e = sys.exc_info()[1]
|
||||
attempt = Attempt(e, attempt_number, True)
|
||||
tb = sys.exc_info()
|
||||
attempt = Attempt(tb, attempt_number, True)
|
||||
|
||||
if not self.should_reject(attempt):
|
||||
return attempt.get(self._wrap_exception)
|
||||
|
@ -189,10 +190,16 @@ class Attempt:
|
|||
if wrap_exception:
|
||||
raise RetryError(self)
|
||||
else:
|
||||
raise self.value
|
||||
raise self.value[0], self.value[1], self.value[2]
|
||||
else:
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
if self.has_exception:
|
||||
return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2])))
|
||||
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.
|
||||
|
@ -202,4 +209,4 @@ class RetryError(Exception):
|
|||
self.last_attempt = last_attempt
|
||||
|
||||
def __str__(self):
|
||||
return "Last attempt: %s" % str(self.last_attempt)
|
||||
return "RetryError[{0}]".format(self.last_attempt)
|
||||
|
|
|
@ -141,7 +141,7 @@ class NoIOErrorAfterCount:
|
|||
"""
|
||||
if self.counter < self.count:
|
||||
self.counter += 1
|
||||
raise IOError()
|
||||
raise IOError("Hi there, I'm an IOError")
|
||||
return True
|
||||
|
||||
class NoNameErrorAfterCount:
|
||||
|
@ -159,7 +159,7 @@ class NoNameErrorAfterCount:
|
|||
"""
|
||||
if self.counter < self.count:
|
||||
self.counter += 1
|
||||
raise NameError()
|
||||
raise NameError("Hi there, I'm a NameError")
|
||||
return True
|
||||
|
||||
class CustomError(Exception):
|
||||
|
@ -201,6 +201,7 @@ def retry_if_result_none(result):
|
|||
|
||||
def retry_if_exception_of_type(retryable_types):
|
||||
def retry_if_exception_these_types(exception):
|
||||
print "Detected Exception of type: " + str(type(exception))
|
||||
return isinstance(exception, retryable_types)
|
||||
return retry_if_exception_these_types
|
||||
|
||||
|
@ -282,8 +283,11 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
try:
|
||||
_retryable_test_with_stop(NoneReturnUntilAfterCount(5))
|
||||
self.fail("Expected RetryError after 3 attempts")
|
||||
except RetryError as e:
|
||||
self.assertEqual(3, e.last_attempt.attempt_number)
|
||||
except RetryError as re:
|
||||
self.assertFalse(re.last_attempt.has_exception)
|
||||
self.assertEqual(3, re.last_attempt.attempt_number)
|
||||
self.assertIsNone(re.last_attempt.value)
|
||||
print re
|
||||
|
||||
def test_retry_if_exception_of_type(self):
|
||||
self.assertTrue(_retryable_test_with_exception_type_io(NoIOErrorAfterCount(5)))
|
||||
|
@ -293,6 +297,7 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
self.fail("Expected NameError")
|
||||
except NameError as n:
|
||||
self.assertTrue(isinstance(n, NameError))
|
||||
print n
|
||||
|
||||
try:
|
||||
_retryable_test_with_exception_type_io_attempt_limit(NoIOErrorAfterCount(5))
|
||||
|
@ -300,7 +305,10 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
except RetryError as re:
|
||||
self.assertEqual(3, re.last_attempt.attempt_number)
|
||||
self.assertTrue(re.last_attempt.has_exception)
|
||||
self.assertTrue(isinstance(re.last_attempt.value, IOError))
|
||||
self.assertIsNotNone(re.last_attempt.value[0])
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], IOError))
|
||||
self.assertIsNotNone(re.last_attempt.value[2])
|
||||
print re
|
||||
|
||||
self.assertTrue(_retryable_test_with_exception_type_custom(NoCustomErrorAfterCount(5)))
|
||||
|
||||
|
@ -309,6 +317,7 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
self.fail("Expected NameError")
|
||||
except NameError as n:
|
||||
self.assertTrue(isinstance(n, NameError))
|
||||
print n
|
||||
|
||||
try:
|
||||
_retryable_test_with_exception_type_custom_attempt_limit(NoCustomErrorAfterCount(5))
|
||||
|
@ -316,7 +325,10 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
except RetryError as re:
|
||||
self.assertEqual(3, re.last_attempt.attempt_number)
|
||||
self.assertTrue(re.last_attempt.has_exception)
|
||||
self.assertTrue(isinstance(re.last_attempt.value, CustomError))
|
||||
self.assertIsNotNone(re.last_attempt.value[0])
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], CustomError))
|
||||
self.assertIsNotNone(re.last_attempt.value[2])
|
||||
print re
|
||||
|
||||
def test_wrapped_exception(self):
|
||||
|
||||
|
@ -326,8 +338,9 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
try:
|
||||
_retryable_test_with_exception_type_io_wrap(NoNameErrorAfterCount(5))
|
||||
self.fail("Expected RetryError")
|
||||
except RetryError as r:
|
||||
self.assertTrue(isinstance(r.last_attempt.value, NameError))
|
||||
except RetryError as re:
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], NameError))
|
||||
print re
|
||||
|
||||
try:
|
||||
_retryable_test_with_exception_type_io_attempt_limit_wrap(NoIOErrorAfterCount(5))
|
||||
|
@ -335,7 +348,10 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
except RetryError as re:
|
||||
self.assertEqual(3, re.last_attempt.attempt_number)
|
||||
self.assertTrue(re.last_attempt.has_exception)
|
||||
self.assertTrue(isinstance(re.last_attempt.value, IOError))
|
||||
self.assertIsNotNone(re.last_attempt.value[0])
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], IOError))
|
||||
self.assertIsNotNone(re.last_attempt.value[2])
|
||||
print re
|
||||
|
||||
# custom error cases
|
||||
self.assertTrue(_retryable_test_with_exception_type_custom_wrap(NoCustomErrorAfterCount(5)))
|
||||
|
@ -343,8 +359,11 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
try:
|
||||
_retryable_test_with_exception_type_custom_wrap(NoNameErrorAfterCount(5))
|
||||
self.fail("Expected RetryError")
|
||||
except RetryError as r:
|
||||
self.assertTrue(isinstance(r.last_attempt.value, NameError))
|
||||
except RetryError as re:
|
||||
self.assertIsNotNone(re.last_attempt.value[0])
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], NameError))
|
||||
self.assertIsNotNone(re.last_attempt.value[2])
|
||||
print re
|
||||
|
||||
try:
|
||||
_retryable_test_with_exception_type_custom_attempt_limit_wrap(NoCustomErrorAfterCount(5))
|
||||
|
@ -352,7 +371,10 @@ class TestDecoratorWrapper(unittest.TestCase):
|
|||
except RetryError as re:
|
||||
self.assertEqual(3, re.last_attempt.attempt_number)
|
||||
self.assertTrue(re.last_attempt.has_exception)
|
||||
self.assertTrue(isinstance(re.last_attempt.value, CustomError))
|
||||
self.assertIsNotNone(re.last_attempt.value[0])
|
||||
self.assertTrue(isinstance(re.last_attempt.value[1], CustomError))
|
||||
self.assertIsNotNone(re.last_attempt.value[2])
|
||||
print re
|
||||
|
||||
def test_defaults(self):
|
||||
self.assertTrue(_retryable_default(NoNameErrorAfterCount(5)))
|
||||
|
|
Loading…
Reference in New Issue