retryable

Decorator for retrying python functions


Keywords
retry
License
Other
Install
pip install retryable==2016.3.11

Documentation

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)