Skip to content

Commit

Permalink
Merge pull request #129 from mrname/feature/raise-on-giveup
Browse files Browse the repository at this point in the history
Feature - Raise on Giveup
  • Loading branch information
bgreen-litl authored Jul 25, 2021
2 parents 4a4b565 + c190573 commit bc33cbb
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 10 deletions.
26 changes: 24 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,31 @@ be retried:
def get_url(url):
return requests.get(url)
When a give up event occurs, the exception in question is reraised
By default, when a give up event occurs, the exception in question is reraised
and so code calling an `on_exception`-decorated function may still
need to do exception handling.
need to do exception handling. This behavior can optionally be disabled
using the `raise_on_giveup` keyword argument.

In the code below, `requests.exceptions.RequestException` will not be raised
when giveup occurs. Note that the decorated function will return `None` in this
case, regardless of the logic in the `on_exception` handler.

.. code-block:: python
def fatal_code(e):
return 400 <= e.response.status_code < 500
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
max_time=300,
raise_on_giveup=False,
giveup=fatal_code)
def get_url(url):
return requests.get(url)
This is useful for non-mission critical code where you still wish to retry
the code inside of `backoff.on_exception` but wish to proceed with execution
even if all retries fail.

@backoff.on_predicate
---------------------
Expand Down
6 changes: 4 additions & 2 deletions backoff/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def retry(*args, **kwargs):
def retry_exception(target, wait_gen, exception,
*,
max_tries, max_time, jitter, giveup,
on_success, on_backoff, on_giveup,
on_success, on_backoff, on_giveup, raise_on_giveup,
wait_gen_kwargs):
on_success = _ensure_coroutines(on_success)
on_backoff = _ensure_coroutines(on_backoff)
Expand Down Expand Up @@ -159,7 +159,9 @@ async def retry(*args, **kwargs):

if giveup_result or max_tries_exceeded or max_time_exceeded:
await _call_handlers(on_giveup, **details)
raise
if raise_on_giveup:
raise
return None

try:
seconds = _next_wait(wait, jitter, elapsed, max_time)
Expand Down
4 changes: 4 additions & 0 deletions backoff/_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def on_exception(wait_gen: _WaitGenerator,
on_success: Optional[_Handler] = None,
on_backoff: Optional[_Handler] = None,
on_giveup: Optional[_Handler] = None,
raise_on_giveup: bool = True,
logger: _MaybeLogger = 'backoff',
backoff_log_level: int = logging.INFO,
giveup_log_level: int = logging.ERROR,
Expand Down Expand Up @@ -169,6 +170,8 @@ def on_exception(wait_gen: _WaitGenerator,
signature to be called in the event that max_tries
is exceeded. The parameter is a dict containing details
about the invocation.
raise_on_giveup: Boolean indicating whether the registered exceptions
should be raised on giveup. Defaults to `True`
logger: Name or Logger object to log to. Defaults to 'backoff'.
backoff_log_level: log level for the backoff event. Defaults to "INFO"
giveup_log_level: log level for the give up event. Defaults to "ERROR"
Expand Down Expand Up @@ -211,6 +214,7 @@ def decorate(target):
on_success=on_success,
on_backoff=on_backoff,
on_giveup=on_giveup,
raise_on_giveup=raise_on_giveup,
wait_gen_kwargs=wait_gen_kwargs
)

Expand Down
6 changes: 4 additions & 2 deletions backoff/_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def retry(*args, **kwargs):
def retry_exception(target, wait_gen, exception,
*,
max_tries, max_time, jitter, giveup,
on_success, on_backoff, on_giveup,
on_success, on_backoff, on_giveup, raise_on_giveup,
wait_gen_kwargs):

@functools.wraps(target)
Expand Down Expand Up @@ -115,7 +115,9 @@ def retry(*args, **kwargs):

if giveup(e) or max_tries_exceeded or max_time_exceeded:
_call_handlers(on_giveup, **details)
raise
if raise_on_giveup:
raise
return None

try:
seconds = _next_wait(wait, jitter, elapsed, max_time)
Expand Down
9 changes: 7 additions & 2 deletions tests/test_backoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ def succeeder(*args, **kwargs):
'tries': 3}


def test_on_exception_giveup():
@pytest.mark.parametrize('raise_on_giveup', [True, False])
def test_on_exception_giveup(raise_on_giveup):
backoffs, giveups, successes = [], [], []

@backoff.on_exception(backoff.constant,
Expand All @@ -270,12 +271,16 @@ def test_on_exception_giveup():
on_giveup=giveups.append,
max_tries=3,
jitter=None,
raise_on_giveup=raise_on_giveup,
interval=0)
@_save_target
def exceptor(*args, **kwargs):
raise ValueError("catch me")

with pytest.raises(ValueError):
if raise_on_giveup:
with pytest.raises(ValueError):
exceptor(1, 2, 3, foo=1, bar=2)
else:
exceptor(1, 2, 3, foo=1, bar=2)

# we try 3 times, backing off twice and giving up once
Expand Down
9 changes: 7 additions & 2 deletions tests/test_backoff_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,22 +235,27 @@ async def succeeder(*args, **kwargs):


@pytest.mark.asyncio
async def test_on_exception_giveup():
@pytest.mark.parametrize('raise_on_giveup', [True, False])
async def test_on_exception_giveup(raise_on_giveup):
log, log_success, log_backoff, log_giveup = _log_hdlrs()

@backoff.on_exception(backoff.constant,
ValueError,
on_success=log_success,
on_backoff=log_backoff,
on_giveup=log_giveup,
raise_on_giveup=raise_on_giveup,
max_tries=3,
jitter=None,
interval=0)
@_save_target
async def exceptor(*args, **kwargs):
raise ValueError("catch me")

with pytest.raises(ValueError):
if raise_on_giveup:
with pytest.raises(ValueError):
await exceptor(1, 2, 3, foo=1, bar=2)
else:
await exceptor(1, 2, 3, foo=1, bar=2)

# we try 3 times, backing off twice and giving up once
Expand Down

0 comments on commit bc33cbb

Please sign in to comment.