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 $