From 239123eb3cb86a26b9752538452ca5f7508705ec Mon Sep 17 00:00:00 2001 From: Martin Boyanov Date: Fri, 5 Jul 2019 10:01:12 +0300 Subject: [PATCH 1/2] Add parameter to call a callback on failure. --- retry/api.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/retry/api.py b/retry/api.py index 4a404b9..24446ff 100644 --- a/retry/api.py +++ b/retry/api.py @@ -11,7 +11,7 @@ def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, - logger=logging_logger): + logger=logging_logger, fail_callback=None): """ Executes a function and retries it if it failed. @@ -25,6 +25,7 @@ def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None, fixed if a number, random if a range tuple (min, max) :param logger: logger.warning(fmt, error, delay) will be called on failed attempts. default: retry.logging_logger. if None, logging is disabled. + :param fail_callback: fail_callback(e) will be called on failed attempts. :returns: the result of the f function. """ _tries, _delay = tries, delay @@ -39,6 +40,9 @@ def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None, if logger is not None: logger.warning('%s, retrying in %s seconds...', e, _delay) + if fail_callback is not None: + fail_callback(e) + time.sleep(_delay) _delay *= backoff @@ -51,7 +55,7 @@ def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None, _delay = min(_delay, max_delay) -def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, logger=logging_logger): +def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, logger=logging_logger, fail_callback=None): """Returns a retry decorator. :param exceptions: an exception or a tuple of exceptions to catch. default: Exception. @@ -63,6 +67,7 @@ def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, ji fixed if a number, random if a range tuple (min, max) :param logger: logger.warning(fmt, error, delay) will be called on failed attempts. default: retry.logging_logger. if None, logging is disabled. + :param fail_callback: fail_callback(e) will be called on failed attempts. :returns: a retry decorator. """ @@ -71,14 +76,15 @@ def retry_decorator(f, *fargs, **fkwargs): args = fargs if fargs else list() kwargs = fkwargs if fkwargs else dict() return __retry_internal(partial(f, *args, **kwargs), exceptions, tries, delay, max_delay, backoff, jitter, - logger) + logger, fail_callback) return retry_decorator def retry_call(f, fargs=None, fkwargs=None, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, - logger=logging_logger): + logger=logging_logger, + fail_callback=None): """ Calls a function and re-executes it if it failed. @@ -94,8 +100,10 @@ def retry_call(f, fargs=None, fkwargs=None, exceptions=Exception, tries=-1, dela fixed if a number, random if a range tuple (min, max) :param logger: logger.warning(fmt, error, delay) will be called on failed attempts. default: retry.logging_logger. if None, logging is disabled. + :param fail_callback: fail_callback(e) will be called on failed attempts. :returns: the result of the f function. """ args = fargs if fargs else list() kwargs = fkwargs if fkwargs else dict() - return __retry_internal(partial(f, *args, **kwargs), exceptions, tries, delay, max_delay, backoff, jitter, logger) + return __retry_internal(partial(f, *args, **kwargs), exceptions, tries, delay, max_delay, backoff, jitter, logger, + fail_callback) From 365158c8b158967c1f7a8f7176ef8be130f0d766 Mon Sep 17 00:00:00 2001 From: Martin Boyanov Date: Fri, 5 Jul 2019 10:29:23 +0300 Subject: [PATCH 2/2] add unit test for fail_callback --- tests/test_retry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_retry.py b/tests/test_retry.py index 64f45cd..7be2573 100644 --- a/tests/test_retry.py +++ b/tests/test_retry.py @@ -183,3 +183,20 @@ def f(value=0): assert result == kwargs['value'] assert f_mock.call_count == 1 + + +def test_retry_call_with_fail_callback(): + + def f(): + raise RuntimeError + + def cb(error): + pass + + callback_mock = MagicMock(spec=cb) + try: + retry_call(f, fail_callback=callback_mock, tries=2) + except RuntimeError: + pass + + callback_mock.assert_called() \ No newline at end of file