propagate complete tracebacks such that they show up in nested stack frames, adjust api and tests #1

This commit is contained in:
Ray Holder 2014-03-30 22:21:53 -05:00
parent f728447c8c
commit f405d7ecc3
2 changed files with 46 additions and 17 deletions

View File

@ -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)

View File

@ -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)))