Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce on_exception handler. #1

Merged
merged 2 commits into from
Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
CHANGES
=======

0.9.4
-----

* Add on_exception parameter accepting a handler called when exception is captured.
The callback can also be used to handle the exception and interrupt further retries.

0.9.3
-----

Expand Down
12 changes: 9 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ retry decorator

.. code:: python

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,
on_exception=None):
"""Return a retry decorator.

:param exceptions: an exception or a tuple of exceptions to catch. default: Exception.
Expand All @@ -52,6 +53,9 @@ retry decorator
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 on_exception: handler called when exception occurs. will be passed the captured
exception as an argument. further retries are stopped when handler
returns True. default: None
"""

Various retrying logic can be achieved by combination of arguments.
Expand Down Expand Up @@ -109,8 +113,7 @@ retry_call
.. code:: python

def retry_call(f, fargs=None, fkwargs=None, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1,
jitter=0,
logger=logging_logger):
jitter=0, logger=logging_logger, on_exception=None):
"""
Calls a function and re-executes it if it failed.

Expand All @@ -126,6 +129,9 @@ retry_call
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 on_exception: handler called when exception occurs. will be passed the captured
exception as an argument. further retries are stopped when handler
returns True. default: None
:returns: the result of the f function.
"""

Expand Down
23 changes: 18 additions & 5 deletions retry/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0,
logger=logging_logger, log_traceback=False):
logger=logging_logger, log_traceback=False, on_exception=None):
"""
Executes a function and retries it if it failed.

Expand All @@ -24,13 +24,20 @@ 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 on_exception: handler called when exception occurs. will be passed the captured
exception as an argument. further retries are stopped when handler
returns True. default: None
:returns: the result of the f function.
"""
_tries, _delay = tries, delay
while _tries:
try:
return f()
except exceptions as e:
if on_exception is not None:
if on_exception(e):
break

_tries -= 1
if not _tries:
raise
Expand Down Expand Up @@ -58,7 +65,7 @@ def __retry_internal(f, exceptions=Exception, tries=-1, delay=0, max_delay=None,


def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, logger=logging_logger,
log_traceback=False):
log_traceback=False, on_exception=None):
"""Returns a retry decorator.

:param exceptions: an exception or a tuple of exceptions to catch. default: Exception.
Expand All @@ -70,6 +77,9 @@ 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 on_exception: handler called when exception occurs. will be passed the captured
exception as an argument. further retries are stopped when handler
returns True. default: None
:returns: a retry decorator.
"""

Expand All @@ -78,13 +88,13 @@ 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, log_traceback=log_traceback)
logger, log_traceback, on_exception)

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, log_traceback=False):
jitter=0, logger=logging_logger, log_traceback=False, on_exception=None):
"""
Calls a function and re-executes it if it failed.

Expand All @@ -100,9 +110,12 @@ 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 on_exception: handler called when exception occurs. will be passed the captured
exception as an argument. further retries are stopped when handler
returns True. default: None
: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,
log_traceback=log_traceback)
log_traceback, on_exception)
11 changes: 11 additions & 0 deletions tests/test_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ def f(value=0):
assert f_mock.call_count == 1


def test_call_on_exception():
exception = RuntimeError()
f_mock = MagicMock(side_effect=exception)
callback_mock = MagicMock()
try:
retry_call(f_mock, tries=1, on_exception=callback_mock)
except RuntimeError:
pass
callback_mock.assert_called_once_with(exception)


def test_logs_function_details(monkeypatch):
mock_sleep_time = [0]

Expand Down