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

add failure_callback param #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ Patches and Suggestions
- Jonathan Herriott
- Job Evers
- Cyrus Durgin
- Daniel Bennett
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ We can also use the result of the function to alter the behavior of retrying.
def might_return_none():
print "Retry forever ignoring Exceptions with no wait if return value is None"

Don't like RetryError on failure? Try running a callback instead.

.. code-block:: python

def return_last_result(attempt):
"""Return the last result of the function instead of raising an exception"""
return attempt.value

@retry(stop_max_attempt_number=3, retry_on_result=retry_if_result_none, failure_callback=return_last_result)
def eventually_return_none():
print("Return None after trying not to")

Any combination of stop, wait, etc. is also supported to give you the freedom to mix and match.

Expand Down
6 changes: 5 additions & 1 deletion retrying.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ def __init__(self,
wait_func=None,
wait_jitter_max=None,
before_attempts=None,
after_attempts=None):
after_attempts=None,
failure_callback=None):

self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number
self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay
Expand All @@ -92,6 +93,7 @@ def __init__(self,
self._wait_jitter_max = 0 if wait_jitter_max is None else wait_jitter_max
self._before_attempts = before_attempts
self._after_attempts = after_attempts
self._failure_callback = failure_callback

# TODO add chaining of stop behaviors
# stop behavior
Expand Down Expand Up @@ -235,6 +237,8 @@ def call(self, fn, *args, **kwargs):

delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
if self.stop(attempt_number, delay_since_first_attempt_ms):
if self._failure_callback:
return self._failure_callback(attempt)
if not self._wrap_exception and attempt.has_exception:
# get() on an attempt with an exception should cause it to be raised, but raise just in case
raise attempt.get()
Expand Down
57 changes: 57 additions & 0 deletions test_retrying.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import time
import unittest

from retrying import Attempt
from retrying import RetryError
from retrying import Retrying
from retrying import retry
Expand Down Expand Up @@ -468,5 +469,61 @@ def _test_after():

self.assertTrue(TestBeforeAfterAttempts._attempt_number is 2)


class TestOnFailure(unittest.TestCase):

_attempts = 3
_attempt_number = 0
_callback_called = False
_exception_message = "Callback should be called instead of this exception being raised."

def tearDown(self):
self._attempt_number = 0
self._callback_called = False

def _callback(self, attempt):
self._callback_called = True
return attempt

def test_failure_callback(self):
@retry(stop_max_attempt_number=self._attempts, failure_callback=self._callback)
def _run():
self._attempt_number += 1
raise Exception(self._exception_message)

# should *not* raise an exception
_run()

self.assertTrue(self._callback_called)
self.assertEqual(self._attempts, self._attempt_number)

def test_failure_callback_callback_receives_attempt(self):
@retry(stop_max_attempt_number=self._attempts, failure_callback=self._callback)
def _run():
self._attempt_number += 1
raise Exception(self._exception_message)

result = _run()

self.assertTrue(isinstance(result, Attempt))

self.assertTrue(self._callback_called)
self.assertEqual(self._attempts, self._attempt_number)

def test_failure_callback_callback_last_attempt_value(self):
@retry(stop_max_attempt_number=self._attempts,
retry_on_result=retry_if_result_none,
failure_callback=self._callback)
def _run():
self._attempt_number += 1

result = _run()

self.assertTrue(result.value is None)

self.assertTrue(self._callback_called)
self.assertEqual(self._attempts, self._attempt_number)


if __name__ == '__main__':
unittest.main()