|
Metadata-Version: 2.1 |
|
Name: backoff |
|
Version: 1.10.0 |
|
Summary: Function decoration for backoff and retry |
|
Home-page: https://github.com/litl/backoff |
|
License: MIT |
|
Keywords: retry,backoff,decorators |
|
Author: Bob Green |
|
Author-email: rgreen@goscoutgo.com |
|
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* |
|
Classifier: Development Status :: 5 - Production/Stable |
|
Classifier: Intended Audience :: Developers |
|
Classifier: License :: OSI Approved :: MIT License |
|
Classifier: Natural Language :: English |
|
Classifier: Operating System :: OS Independent |
|
Classifier: Programming Language :: Python |
|
Classifier: Programming Language :: Python :: 2 |
|
Classifier: Programming Language :: Python :: 2.7 |
|
Classifier: Programming Language :: Python :: 3 |
|
Classifier: Programming Language :: Python :: 3.5 |
|
Classifier: Programming Language :: Python :: 3.6 |
|
Classifier: Programming Language :: Python :: 3.7 |
|
Classifier: Programming Language :: Python :: 3.8 |
|
Classifier: Topic :: Internet :: WWW/HTTP |
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules |
|
Classifier: Topic :: Utilities |
|
Project-URL: Repository, https://github.com/litl/backoff |
|
Description-Content-Type: text/x-rst |
|
|
|
backoff |
|
======= |
|
|
|
.. image:: https://travis-ci.org/litl/backoff.svg?branch=master |
|
:target: https://travis-ci.org/litl/backoff?branch=master |
|
.. image:: https://coveralls.io/repos/litl/backoff/badge.svg?branch=master |
|
:target: https://coveralls.io/r/litl/backoff?branch=master |
|
.. image:: https://img.shields.io/pypi/v/backoff.svg |
|
:target: https://pypi.python.org/pypi/backoff |
|
|
|
**Function decoration for backoff and retry** |
|
|
|
This module provides function decorators which can be used to wrap a |
|
function such that it will be retried until some condition is met. It |
|
is meant to be of use when accessing unreliable resources with the |
|
potential for intermittent failures i.e. network resources and external |
|
APIs. Somewhat more generally, it may also be of use for dynamically |
|
polling resources for externally generated content. |
|
|
|
Decorators support both regular functions for synchronous code and |
|
`asyncio <https://docs.python.org/3/library/asyncio.html>`_'s coroutines |
|
for asynchronous code. |
|
|
|
Examples |
|
======== |
|
|
|
Since Kenneth Reitz's `requests <http://python-requests.org>`_ module |
|
has become a defacto standard for synchronous HTTP clients in Python, |
|
networking examples below are written using it, but it is in no way required |
|
by the backoff module. |
|
|
|
@backoff.on_exception |
|
--------------------- |
|
|
|
The ``on_exception`` decorator is used to retry when a specified exception |
|
is raised. Here's an example using exponential backoff when any |
|
``requests`` exception is raised: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.RequestException) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
The decorator will also accept a tuple of exceptions for cases where |
|
the same backoff behavior is desired for more than one exception type: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
(requests.exceptions.Timeout, |
|
requests.exceptions.ConnectionError)) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
**Give Up Conditions** |
|
|
|
Optional keyword arguments can specify conditions under which to give |
|
up. |
|
|
|
The keyword argument ``max_time`` specifies the maximum amount |
|
of total time in seconds that can elapse before giving up. |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.RequestException, |
|
max_time=60) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
|
|
Keyword argument ``max_tries`` specifies the maximum number of calls |
|
to make to the target function before giving up. |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.RequestException, |
|
max_tries=8, |
|
jitter=None) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
|
|
In some cases the raised exception instance itself may need to be |
|
inspected in order to determine if it is a retryable condition. The |
|
``giveup`` keyword arg can be used to specify a function which accepts |
|
the exception and returns a truthy value if the exception should not |
|
be retried: |
|
|
|
.. 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, |
|
giveup=fatal_code) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
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. |
|
|
|
@backoff.on_predicate |
|
--------------------- |
|
|
|
The ``on_predicate`` decorator is used to retry when a particular |
|
condition is true of the return value of the target function. This may |
|
be useful when polling a resource for externally generated content. |
|
|
|
Here's an example which uses a fibonacci sequence backoff when the |
|
return value of the target function is the empty list: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13) |
|
def poll_for_messages(queue): |
|
return queue.get() |
|
|
|
Extra keyword arguments are passed when initializing the |
|
wait generator, so the ``max_value`` param above is passed as a keyword |
|
arg when initializing the fibo generator. |
|
|
|
When not specified, the predicate param defaults to the falsey test, |
|
so the above can more concisely be written: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_predicate(backoff.fibo, max_value=13) |
|
def poll_for_message(queue) |
|
return queue.get() |
|
|
|
More simply, a function which continues polling every second until it |
|
gets a non-falsey result could be defined like like this: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_predicate(backoff.constant, interval=1) |
|
def poll_for_message(queue) |
|
return queue.get() |
|
|
|
Jitter |
|
------ |
|
|
|
A jitter algorithm can be supplied with the ``jitter`` keyword arg to |
|
either of the backoff decorators. This argument should be a function |
|
accepting the original unadulterated backoff value and returning it's |
|
jittered counterpart. |
|
|
|
As of version 1.2, the default jitter function ``backoff.full_jitter`` |
|
implements the 'Full Jitter' algorithm as defined in the AWS |
|
Architecture Blog's `Exponential Backoff And Jitter |
|
<https://www.awsarchitectureblog.com/2015/03/backoff.html>`_ post. |
|
Note that with this algorithm, the time yielded by the wait generator |
|
is actually the *maximum* amount of time to wait. |
|
|
|
Previous versions of backoff defaulted to adding some random number of |
|
milliseconds (up to 1s) to the raw sleep value. If desired, this |
|
behavior is now available as ``backoff.random_jitter``. |
|
|
|
Using multiple decorators |
|
------------------------- |
|
|
|
The backoff decorators may also be combined to specify different |
|
backoff behavior for different cases: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_predicate(backoff.fibo, max_value=13) |
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.HTTPError, |
|
max_time=60) |
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.Timeout, |
|
max_time=300) |
|
def poll_for_message(queue): |
|
return queue.get() |
|
|
|
Runtime Configuration |
|
--------------------- |
|
|
|
The decorator functions ``on_exception`` and ``on_predicate`` are |
|
generally evaluated at import time. This is fine when the keyword args |
|
are passed as constant values, but suppose we want to consult a |
|
dictionary with configuration options that only become available at |
|
runtime. The relevant values are not available at import time. Instead, |
|
decorator functions can be passed callables which are evaluated at |
|
runtime to obtain the value: |
|
|
|
.. code-block:: python |
|
|
|
def lookup_max_time(): |
|
|
|
|
|
return app.config["BACKOFF_MAX_TIME"] |
|
|
|
@backoff.on_exception(backoff.expo, |
|
ValueError, |
|
max_time=lookup_max_time) |
|
|
|
Event handlers |
|
-------------- |
|
|
|
Both backoff decorators optionally accept event handler functions |
|
using the keyword arguments ``on_success``, ``on_backoff``, and ``on_giveup``. |
|
This may be useful in reporting statistics or performing other custom |
|
logging. |
|
|
|
Handlers must be callables with a unary signature accepting a dict |
|
argument. This dict contains the details of the invocation. Valid keys |
|
include: |
|
|
|
* *target*: reference to the function or method being invoked |
|
* *args*: positional arguments to func |
|
* *kwargs*: keyword arguments to func |
|
* *tries*: number of invocation tries so far |
|
* *elapsed*: elapsed time in seconds so far |
|
* *wait*: seconds to wait (``on_backoff`` handler only) |
|
* *value*: value triggering backoff (``on_predicate`` decorator only) |
|
|
|
A handler which prints the details of the backoff event could be |
|
implemented like so: |
|
|
|
.. code-block:: python |
|
|
|
def backoff_hdlr(details): |
|
print ("Backing off {wait:0.1f} seconds afters {tries} tries " |
|
"calling function {target} with args {args} and kwargs " |
|
"{kwargs}".format(**details)) |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.RequestException, |
|
on_backoff=backoff_hdlr) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
**Multiple handlers per event type** |
|
|
|
In all cases, iterables of handler functions are also accepted, which |
|
are called in turn. For example, you might provide a simple list of |
|
handler functions as the value of the ``on_backoff`` keyword arg: |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exceptions.RequestException, |
|
on_backoff=[backoff_hdlr1, backoff_hdlr2]) |
|
def get_url(url): |
|
return requests.get(url) |
|
|
|
**Getting exception info** |
|
|
|
In the case of the ``on_exception`` decorator, all ``on_backoff`` and |
|
``on_giveup`` handlers are called from within the except block for the |
|
exception being handled. Therefore exception info is available to the |
|
handler functions via the python standard library, specifically |
|
``sys.exc_info()`` or the ``traceback`` module. |
|
|
|
Asynchronous code |
|
----------------- |
|
|
|
Backoff supports asynchronous execution in Python 3.5 and above. |
|
|
|
To use backoff in asynchronous code based on |
|
`asyncio <https://docs.python.org/3/library/asyncio.html>`_ |
|
you simply need to apply ``backoff.on_exception`` or ``backoff.on_predicate`` |
|
to coroutines. |
|
You can also use coroutines for the ``on_success``, ``on_backoff``, and |
|
``on_giveup`` event handlers, with the interface otherwise being identical. |
|
|
|
The following examples use `aiohttp <https://aiohttp.readthedocs.io/>`_ |
|
asynchronous HTTP client/server library. |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60) |
|
async def get_url(url): |
|
async with aiohttp.ClientSession() as session: |
|
async with session.get(url) as response: |
|
return await response.text() |
|
|
|
Logging configuration |
|
--------------------- |
|
|
|
By default, backoff and retry attempts are logged to the 'backoff' |
|
logger. By default, this logger is configured with a NullHandler, so |
|
there will be nothing output unless you configure a handler. |
|
Programmatically, this might be accomplished with something as simple |
|
as: |
|
|
|
.. code-block:: python |
|
|
|
logging.getLogger('backoff').addHandler(logging.StreamHandler()) |
|
|
|
The default logging level is INFO, which corresponds to logging |
|
anytime a retry event occurs. If you would instead like to log |
|
only when a giveup event occurs, set the logger level to ERROR. |
|
|
|
.. code-block:: python |
|
|
|
logging.getLogger('backoff').setLevel(logging.ERROR) |
|
|
|
It is also possible to specify an alternate logger with the ``logger`` |
|
keyword argument. If a string value is specified the logger will be |
|
looked up by name. |
|
|
|
.. code-block:: python |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exception.RequestException, |
|
logger='my_logger') |
|
|
|
|
|
It is also supported to specify a Logger (or LoggerAdapter) object |
|
directly. |
|
|
|
.. code-block:: python |
|
|
|
my_logger = logging.getLogger('my_logger') |
|
my_handler = logging.StreamHandler() |
|
my_logger.add_handler(my_handler) |
|
my_logger.setLevel(logging.ERROR) |
|
|
|
@backoff.on_exception(backoff.expo, |
|
requests.exception.RequestException, |
|
logger=my_logger) |
|
|
|
|
|
Default logging can be disabled all together by specifying |
|
``logger=None``. In this case, if desired alternative logging behavior |
|
could be defined by using custom event handlers. |
|
|
|
|