saltycrane/retry-decorator — pull request #2 by stoduk (2014-05-23 10:07:17+00:00)
Title: Enhance retry decorator to allow catching exception instances

Enhance retry decorator to allow not just catching of types of exceptions (eg. IOError), but specific instances of those exceptions (eg. catch IOError(errno.ECOMM), but not IOError(errno.EINVAL))

Also add "silent" flag to allow no output from decorators

UT modified to match these changes, details below, also see test output

Extended existing tests:
========================
a) pass without my change:
test_no_retry_required_instance - will pass without changes (as it succeeds always)
test_unexpected_exception_does_not_retry_instance - passing exception instance doesn't affect the test, the UnexpectedError is still uncaught

b) fail without my change, pass with it:
test_retries_once_instance - fails to catch specific exception instance, without my change
test_multiple_exception_types_instance - ditto
test_multiple_exception_types_mixed1 - ditto
test_multiple_exception_types_mixed2 - ditto
test_limit_is_reached_instance - again, specific exception instance is uncaught, but test code catches it - yet we can see no retry is done from counter (so hit assertion)

New tests:
==========
Most will pass without my change, as exception instances are never caught
(these are mostly negative tests to check my change doesn't swallow more exceptions than it should -
eg. RetryableError(errno.EINVAL) when only RetryableError(errno.ECOMM) is specified))

a) pass without my change:
test_specific_exception_type_uncaught - really just a quick sanity test of my new function :)
test_specific_exception_type_caught1 - ditto
test_specific_exception_type_caught2 - ditto
test_specific_exception_instance_caught2
test_specific_exception_instance_uncaught1
test_specific_exception_instance_uncaught2

b) fail without my change, pass with it:
test_specific_exception_instance_caught1 - fails without my change, passes with

Test output
=========
- unchanged code
- patch in test changes, run on unchanged code - some new failures
- patch in real change - all tests pass

pi@RPi-Printer ~/mopi/retry-decorator $ git diff
pi@RPi-Printer ~/mopi/retry-decorator $ python test_decorators.py
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
.a retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
..failed, Retrying in 0 seconds...
..failed, Retrying in 0 seconds...
.
----------------------------------------------------------------------
Ran 6 tests in 1.234s

OK
pi@RPi-Printer ~/mopi/retry-decorator $
pi@RPi-Printer ~/mopi/retry-decorator $ patch < ../retry_decorator.ut.final.diff
patching file test_decorators.py
pi@RPi-Printer ~/mopi/retry-decorator $ python test_decorators.py
Ffailed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
.Ea retryable error, Retrying in 0 seconds...
EEa retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
...Efailed, Retrying in 0 seconds...
.E70, Retrying in 0 seconds...
..., Retrying in 0 seconds...
., Retrying in 0 seconds...
....failed, Retrying in 0 seconds...
.
======================================================================
ERROR: test_multiple_exception_types_instance (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 93, in test_multiple_exception_types_instance
self._multiple_exception_types((RetryableError('a retryable error'), AnotherRetryableError('another retryable error')))
File "test_decorators.py", line 87, in _multiple_exception_types
r = raise_multiple_exceptions()
File "/home/pi/mopi/retry-decorator/decorators.py", line 31, in f_retry
return f(*args, **kwargs)
File "test_decorators.py", line 81, in raise_multiple_exceptions
raise RetryableError('a retryable error')
RetryableError: a retryable error

======================================================================
ERROR: test_multiple_exception_types_mixed1 (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 95, in test_multiple_exception_types_mixed1
self._multiple_exception_types((RetryableError, AnotherRetryableError('another retryable error')))
File "test_decorators.py", line 87, in _multiple_exception_types
r = raise_multiple_exceptions()
File "/home/pi/mopi/retry-decorator/decorators.py", line 31, in f_retry
return f(*args, **kwargs)
File "test_decorators.py", line 83, in raise_multiple_exceptions
raise AnotherRetryableError('another retryable error')
AnotherRetryableError: another retryable error

======================================================================
ERROR: test_multiple_exception_types_mixed2 (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 97, in test_multiple_exception_types_mixed2
self._multiple_exception_types((RetryableError('a retryable error'), AnotherRetryableError))
File "test_decorators.py", line 87, in _multiple_exception_types
r = raise_multiple_exceptions()
File "/home/pi/mopi/retry-decorator/decorators.py", line 31, in f_retry
return f(*args, **kwargs)
File "test_decorators.py", line 81, in raise_multiple_exceptions
raise RetryableError('a retryable error')
RetryableError: a retryable error

======================================================================
ERROR: test_retries_once_instance (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 56, in test_retries_once_instance
self._retries_once(RetryableError('failed'))
File "test_decorators.py", line 50, in _retries_once
r = fails_once()
File "/home/pi/mopi/retry-decorator/decorators.py", line 31, in f_retry
return f(*args, **kwargs)
File "test_decorators.py", line 46, in fails_once
raise RetryableError('failed')
RetryableError: failed

======================================================================
ERROR: test_specific_exception_instance_caught1 (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 148, in test_specific_exception_instance_caught1
self._specific_exception(RetryableError(errno.ECOMM), True)
File "test_decorators.py", line 127, in _specific_exception
r = fails_once()
File "/home/pi/mopi/retry-decorator/decorators.py", line 31, in f_retry
return f(*args, **kwargs)
File "test_decorators.py", line 121, in fails_once
raise exception
RetryableError: 70

======================================================================
FAIL: test_limit_is_reached_instance (__main__.RetryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_decorators.py", line 72, in test_limit_is_reached_instance
self._limit_is_reached(RetryableError('failed'))
File "test_decorators.py", line 68, in _limit_is_reached
self.assertEqual(self.counter, 4)
AssertionError: 1 != 4

----------------------------------------------------------------------
Ran 20 tests in 1.685s

FAILED (failures=1, errors=5)
pi@RPi-Printer ~/mopi/retry-decorator $
pi@RPi-Printer ~/mopi/retry-decorator $ patch < ../retry_decorator.final.diff
patching file decorators.py
pi@RPi-Printer ~/mopi/retry-decorator $ python test_decorators.py
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
.failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
failed, Retrying in 0 seconds...
.a retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
.a retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
.a retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
.a retryable error, Retrying in 0 seconds...
another retryable error, Retrying in 0 seconds...
...failed, Retrying in 0 seconds...
.failed, Retrying in 0 seconds...
.70, Retrying in 0 seconds...
.70, Retrying in 0 seconds...
...AnotherRetryableError(), Retrying in 0 seconds...
.UnexpectedError(), Retrying in 0 seconds...
....failed, Retrying in 0 seconds...
.
----------------------------------------------------------------------
Ran 20 tests in 3.407s

OK
pi@RPi-Printer ~/mopi/retry-decorator $