Spaces:
Runtime error
Runtime error
Metadata-Version: 2.1 | |
Name: backoff | |
Version: 2.2.1 | |
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: [email protected] | |
Requires-Python: >=3.7,<4.0 | |
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 :: 3 | |
Classifier: Programming Language :: Python :: 3.7 | |
Classifier: Programming Language :: Python :: 3.8 | |
Classifier: Programming Language :: Python :: 3.9 | |
Classifier: Programming Language :: Python :: 3.10 | |
Classifier: Programming Language :: Python :: 3 | |
Classifier: Programming Language :: Python :: 3.10 | |
Classifier: Programming Language :: Python :: 3.7 | |
Classifier: Programming Language :: Python :: 3.8 | |
Classifier: Programming Language :: Python :: 3.9 | |
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 | |
:target: https://travis-ci.org/litl/backoff | |
.. image:: https://coveralls.io/repos/litl/backoff/badge.svg | |
:target: https://coveralls.io/r/litl/backoff?branch=python-3 | |
.. image:: https://github.com/litl/backoff/workflows/CodeQL/badge.svg | |
:target: https://github.com/litl/backoff/actions/workflows/codeql-analysis.yml | |
.. image:: https://img.shields.io/pypi/v/backoff.svg | |
:target: https://pypi.python.org/pypi/backoff | |
.. image:: https://img.shields.io/github/license/litl/backoff | |
:target: https://github.com/litl/backoff/blob/master/LICENSE | |
**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) | |
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. 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 | |
--------------------- | |
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, jitter=None, interval=1) | |
def poll_for_message(queue): | |
return queue.get() | |
The jitter is disabled in order to keep the polling frequency fixed. | |
@backoff.runtime | |
---------------- | |
You can also use the ``backoff.runtime`` generator to make use of the | |
return value or thrown exception of the decorated method. | |
For example, to use the value in the ``Retry-After`` header of the response: | |
.. code-block:: python | |
@backoff.on_predicate( | |
backoff.runtime, | |
predicate=lambda r: r.status_code == 429, | |
value=lambda r: int(r.headers.get("Retry-After")), | |
jitter=None, | |
) | |
def get_url(): | |
return requests.get(url) | |
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(): | |
# pretend we have a global reference to 'app' here | |
# and that it has a dictionary-like 'config' property | |
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 after {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. The exception is also | |
available at the *exception* key in the `details` dict passed to the | |
handlers. | |
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(raise_for_status=True) 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.exceptions.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.addHandler(my_handler) | |
my_logger.setLevel(logging.ERROR) | |
@backoff.on_exception(backoff.expo, | |
requests.exceptions.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. | |