Skip to content

Commit

Permalink
fixed a bug where classes not extending from the Python exception hie…
Browse files Browse the repository at this point in the history
…rarchy could slip through, preparing for 1.0.1 release
  • Loading branch information
rholder committed Mar 20, 2013
1 parent a7a6a43 commit 8035a32
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Expand Up @@ -3,6 +3,11 @@
History
-------

1.0.1 (2013-03-20)
++++++++++++++++++
- Fixed a bug where classes not extending from the Python exception hierarchy could slip through
- Update test suite for custom Python exceptions

1.0.0 (2013-01-21)
++++++++++++++++++
- First stable, tested version now exists
Expand Down
4 changes: 3 additions & 1 deletion retrying.py
Expand Up @@ -13,6 +13,7 @@
## limitations under the License.

import random
import sys
import time

# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
Expand Down Expand Up @@ -150,7 +151,8 @@ def call(self, fn, *args, **kwargs):
while True:
try:
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
except BaseException as e:
except:
e = sys.exc_info()[1]
attempt = Attempt(e, attempt_number, True)

if not self.should_reject(attempt):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -18,7 +18,7 @@

settings.update(
name='retrying',
version='1.0.0',
version='1.0.1',
description='Retrying',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
Expand Down
92 changes: 90 additions & 2 deletions test_retrying.py
Expand Up @@ -162,6 +162,34 @@ def go(self):
raise NameError()
return True

class CustomError:
"""
This is a custom exception class that doesn't inherit from any of the Python base Exception hierarchy.
"""

def __init__(self, value):
self.value = value

def __str__(self):
return repr(self.value)

class NoCustomErrorAfterCount:
"""
This class holds counter state for invoking a method several times in a row.
"""

def __init__(self, count):
self.counter = 0
self.count = count

def go(self):
"""
Raise a CustomError until after count threshold has been crossed, then return True.
"""
if self.counter < self.count:
self.counter += 1
raise CustomError("This is a Custom exception class")
return True

def retry_if_result_none(result):
return result is None
Expand Down Expand Up @@ -213,6 +241,29 @@ def _retryable_default(thing):
def _retryable_default_f(thing):
return thing.go()

@retry(retry_on_exception=retry_if_exception_of_type(CustomError))
def _retryable_test_with_exception_type_custom(thing):
return thing.go()

@retry(retry_on_exception=retry_if_exception_of_type(CustomError), wrap_exception=True)
def _retryable_test_with_exception_type_custom_wrap(thing):
return thing.go()

@retry(
stop='stop_after_attempt',
stop_max_attempt_number=3,
retry_on_exception=retry_if_exception_of_type(CustomError))
def _retryable_test_with_exception_type_custom_attempt_limit(thing):
return thing.go()

@retry(
stop='stop_after_attempt',
stop_max_attempt_number=3,
retry_on_exception=retry_if_exception_of_type(CustomError),
wrap_exception=True)
def _retryable_test_with_exception_type_custom_attempt_limit_wrap(thing):
return thing.go()

class TestDecoratorWrapper(unittest.TestCase):

def test_with_wait(self):
Expand Down Expand Up @@ -240,13 +291,31 @@ def test_retry_if_exception_of_type(self):

try:
_retryable_test_with_exception_type_io_attempt_limit(NoIOErrorAfterCount(5))
self.fail("RetryError expected")
self.fail("Expected RetryError")
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.assertTrue(_retryable_test_with_exception_type_custom(NoCustomErrorAfterCount(5)))

try:
_retryable_test_with_exception_type_custom(NoNameErrorAfterCount(5))
self.fail("Expected NameError")
except NameError as n:
self.assertTrue(isinstance(n, NameError))

try:
_retryable_test_with_exception_type_custom_attempt_limit(NoCustomErrorAfterCount(5))
self.fail("Expected RetryError")
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))

def test_wrapped_exception(self):

# base exception cases
self.assertTrue(_retryable_test_with_exception_type_io_wrap(NoIOErrorAfterCount(5)))

try:
Expand All @@ -257,15 +326,34 @@ def test_wrapped_exception(self):

try:
_retryable_test_with_exception_type_io_attempt_limit_wrap(NoIOErrorAfterCount(5))
self.fail("RetryError expected")
self.fail("Expected RetryError")
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))

# custom error cases
self.assertTrue(_retryable_test_with_exception_type_custom_wrap(NoCustomErrorAfterCount(5)))

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

try:
_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)
self.assertTrue(re.last_attempt.has_exception)
self.assertTrue(isinstance(re.last_attempt.value, CustomError))

def test_defaults(self):
self.assertTrue(_retryable_default(NoNameErrorAfterCount(5)))
self.assertTrue(_retryable_default_f(NoNameErrorAfterCount(5)))
self.assertTrue(_retryable_default(NoCustomErrorAfterCount(5)))
self.assertTrue(_retryable_default_f(NoCustomErrorAfterCount(5)))

if __name__ == '__main__':
unittest.main()

0 comments on commit 8035a32

Please sign in to comment.