Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kanzure/retryable

Python Retry Decorator
https://github.com/kanzure/retryable

Last synced: about 1 month ago
JSON representation

Python Retry Decorator

Awesome Lists containing this project

README

        

Retryable Python Functions
==========================

This decorator wraps any function with retry logic. After wrapping a function with this via::
from retryable import retry

@retry()
def some_function(...):
pass

def some_function(...):
pass

retryable_func = retry(some_function)

If all retry attempts fail, the wrapper will raise the exception caught during the last attempt.
It will attach the ``retry_count`` attribute to the exception before it throws it. The caller
can then inspect the exception to see how many retries were attempted.

If there are no retries attempted and the function raises, the original exception raised by the
function will be passed up to the caller to be handled.

Controlling Retry Logic
=======================

The retry logic can be controlled via a few different ways:

No Retry List
=============

If you have a list of exceptions that you know should not cause a retry, you can pass them into the
decorator via the ``no_retry_list`` parameter. If any exception in the ``no_retry_list`` parameter
is caught during the execution of the wrapped function it will be reraised without a retry attempt.
For example::

# Do not retry if we get an OSError or an AttributeError
@retry(no_retry_list=[OSError, AttributeError])
def some_function(some_condition):
if some_condition == 'os_error':
raise OSError
elif some_condition == 'attr_error':
raise AttributeError
else:
raise RuntimeError

# this will cause an immediate raise of OSError with no retries
some_function('os_error')

# This will cause a number of retries and then finally raise the RuntimeError thrown by the func.
some_function('retry')

If ``no_rety_list`` is empty any exception will cause a retry.

Retries
=============

Number of tries. Useful when there is a retryable but we don't want to specify an _retry_count
keyword argument each time the retryable is invoked.

Order of precedence: retries value, followed by _retry_count. Defaults to None.

Retry Callable
==============

If the decision on whether to retry is more complex than just a simple Exception type you can provide
a callable that will be used to determine if a call should be retried. This callable is passed via the
``_retry_callback`` kwarg into the wrapped function. The _retry_callback is then called when an
exception is caught, passing in the caught exception. The _retry_callback should return ``True``
if a retry should be attempted or ``False`` if not. A basic _retry_callback would look like this::

# We are passed the exception that was caught
def retry_filter(exc):
# Do some kind of checking to determine if we should retry
if hasattr(exc, 'retryflag'):
return True
else:
return False

How this would be used in a retryable function::

@retry
def some_function(condition):
if condition == 'raise_retry':
# Raise a RuntimeError with the retryflag set
e = RuntimeError()
e.retryflag = True
raise e
else:
# Raise a normal RuntimeError with no flag
raise RuntimeError()

# Will retry
some_function('raise_retry', _retry_callback=retry_filter)

# Will not retry
some_function('no_raise_retry', _retry_callback=retry_filter)

Note: The supplied ``_retry_callback`` will only be executed for exceptions that are not in the
``no_retry_list``.

Retry behavior
==============

You can pass kwargs to control the retry behavior of a wrapped function. This allows you to override
the retry behavior at runtime. The kwargs you can pass are:
_retry_count: This is the number attempts to try
_retry_delay: The delay to use. This delay will be used on the first retry attempt and then
increased by ^_retry_backoff for each subsequent attempt.
_retry_backoff: The factor to successively multiply the delay by for the next attempt.

These params are passed when the wrapped function is called, not when it is decorated. Ie::
@retry
def some_function():
raise err

# Retry this up to 3 times and start with a delay of half a second.
ret = some_function(_retry_count=3, _retry_delay=.5)