Skip to main content

Python package of retry utilities named after the English anarcho-communist rock band Chumbawamba's 1997 hit Tubthumping

Project description

Tubthumper: Helping you get up ... again!

CI/CD: n/a Docs: n/a Downloads: n/a PyPI: n/a codecov: n/a MyPY: n/a Pylint: n/a


What's in a name?

Tubthumper is a Python package of retry utilities named after the English anarcho-communist rock band Chumbawamba's 1997 hit Tubthumping. Yes, really.

I get knocked down, but I get up again. 🎶
You're never gonna keep me down. 🎶
I get knocked down, but I get up again. 🎶
You're never gonna keep me down... 🎶

Getting Started

Installation

tubthumper is a pip-installable package hosted on PyPI. Getting started is as easy as:

$ pip install tubthumper

tubthumper requires Python 3.6 or greater. For Python 3.8 or greater, it has no external dependencies, i.e. standard library only, but earlier versions require the dataclasses backport and typing-extensions.

Usage

Import tubthumper's useful bits:

>>> from tubthumper import retry, retry_decorator, retry_factory

Call a function with retry and jittered exponential backoff:

>>> retry(get_ip, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 0.844422 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Call that same function with positional and keyword arguments, e.g. retry get_ip(42, "test", dev=True):

>>> retry(get_ip,
...     args=(42, "test"), kwargs={"dev": True},
...     exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 0.420572 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Bake retry behavior into your function with a decorator:

>>> @retry_decorator(exceptions=ConnectionError)
... def get_ip_retry():
...     return requests.get("http://ip.jsontest.com").json()
>>> get_ip_retry()
WARNING: Function threw exception below on try 1, retrying in 0.511275 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Create a new function with retry behavior from an existing one:

>>> get_ip_retry = retry_factory(get_ip, exceptions=ConnectionError)
>>> get_ip_retry()
WARNING: Function threw exception below on try 1, retrying in 0.783799 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Customization

While tubthumper ships with a set of sensible defaults, its retry behavior is fully customizable.

Exceptions

Because overbroad except clauses are the most diabolical Python antipattern, there is no sensible default for what exception or exceptions to catch and retry. Thus, every tubthumper interface has a required exceptions keyword-only argument, which takes an exception or tuple of exceptions to catch and retry on, i.e. a sensible lack of a default.

>>> retry(get_ip, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 0.476597 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}
>>> retry(get_ip, exceptions=(KeyError, ConnectionError))
WARNING: Function threw exception below on try 1, retrying in 0.908113 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

By default, tubthumper raises a tubthumper.RetryError exception when all retries have been exhausted:

>>> retry(lambda: 1/0, retry_limit=0, exceptions=ZeroDivisionError)
Traceback (most recent call last):
  ...
tubthumper._retry_factory.RetryError: Retry limit 0 reached

You can override this behavior using the reraise flag to reraise the original exception in place of RetryError:

>>> retry(lambda: 1/0, retry_limit=0, reraise=True, exceptions=ZeroDivisionError)
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

Retry Limits

By default, tubthumper will retry endlessly, but you have two means of limiting retry behavior. As shown previously, to limit the number of retries attempted, use the retry_limit keyword-only argument:

>>> retry(lambda: 1/0, retry_limit=10, exceptions=ZeroDivisionError)
...  # Warning logs for each failed call
Traceback (most recent call last):
  ...
tubthumper._retry_factory.RetryError: Retry limit 10 reached

Alternatively, you can use the time_limit keyword-only argument to prevent retry attempts after a certain duration:

>>> retry(lambda: 1/0, time_limit=60, exceptions=ZeroDivisionError)
...  # Warning logs for each failed call
Traceback (most recent call last):
  ...
tubthumper._retry_factory.RetryError: Time limit 60 exceeded

Backoff timing

By default, the backoff duration doubles with each retry, starting off at one second. As well, each backoff period is jittered, i.e. scaled by a uniformly distributed random number on the [0.0, 1.0) interval. You can disable jittering using the jitter keyword-only argument:

>>> retry(get_ip, jitter=False, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 1 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

You can set the initial backoff duration using the init_backoff keyword-only argument:

>>> retry(get_ip, jitter=False, init_backoff=10, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 10 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Finally, you can set the factor by which each successive backoff duration is scaled using the exponential keyword-only argument:

>>> retry(get_ip, jitter=False, exponential=3, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 1 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
WARNING: Function threw exception below on try 2, retrying in 3 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Logging

By default, tubthumper logs each caught exception at the logging.WARNING level using a logger named tubthumper, i.e. logging.getLogger("tubthumper"). As described in the Python logging tutorial, for this default logger, "events of severity WARNING and greater will be printed to sys.stderr" if no further logging is configured.

You can set the logging level using the log_level keyword-only argument:

>>> retry(get_ip, log_level=logging.DEBUG, exceptions=ConnectionError) # No warnings
{'ip': '8.8.8.8'}

You can provide your own logger using the logger keyword-only argument. This logger's log method will be called like so:

logger.log(log_level, "Function threw...", exc_info=True)

Features

Compatible with methods

tubthumper's various interfaces are compatible with methods, including classmethods and staticmethods:

>>> class Class:
...     @retry_decorator(exceptions=ConnectionError)
...     def get_ip(self):
...         return requests.get("http://ip.jsontest.com").json()
...
>>> Class().get_ip()
WARNING: Function threw exception below on try 1, retrying in 0.260492 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Signature preserving

tubthumper's various interfaces preserve the relevant dunder attributes of your function:

>>> @retry_decorator(exceptions=ConnectionError)
... def func(one: bool, two: float = 3.0) -> complex:
...     """This is a docstring"""
...
>>> func.__name__
'func'
>>> func.__qualname__
'func'
>>> func.__module__
'__main__'
>>> func.__doc__
'This is a docstring'
>>> func.__annotations__
{'one': <class 'bool'>, 'two': <class 'float'>, 'return': <class 'complex'>}

tubthumper also preserves the inspect module's function signature, and is* functions:

>>> import inspect
>>> inspect.signature(func)
<Signature (one: bool, two: float = 3.0) -> complex>
>>> inspect.isfunction(func)
True
>>> inspect.isroutine(func)
True
>>> inspect.ismethod(Class().get_ip)
True

Async support

tubthumper's various interfaces support coroutine functions, including generator-based coroutines, awaiting them while using async.sleep between awaits:

>>> @retry_decorator(exceptions=ConnectionError)
... async def get_ip():
...     return requests.get("http://ip.jsontest.com").json()
...
>>> inspect.iscoroutinefunction(get_ip)
True

Fully type annotated

tubthumper's various interfaces are fully type annotated, passing Mypy's static type checker. You can find Mypy's Type Check Coverage Summary at that link.

100% Test Coverage

tubthumper achieves 100% test coverage across three supported operating systems (Windows, MacOS, & Linux). You can find the Linux coverage report at that link, or the full coverage report on Codecov.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tubthumper-0.0.0.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

tubthumper-0.0.0-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file tubthumper-0.0.0.tar.gz.

File metadata

  • Download URL: tubthumper-0.0.0.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.7

File hashes

Hashes for tubthumper-0.0.0.tar.gz
Algorithm Hash digest
SHA256 1289e4a83442335e7e58074b1a8420df3c32b8895c5fbef1600e3b8bfacd3e22
MD5 240b21fc3fed8fa3daf739aa9a3c293b
BLAKE2b-256 d204a456e161059ddab492b2d02b235060c1e9d97296df261b7f4732af09c6b5

See more details on using hashes here.

File details

Details for the file tubthumper-0.0.0-py3-none-any.whl.

File metadata

  • Download URL: tubthumper-0.0.0-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.7

File hashes

Hashes for tubthumper-0.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 238bdeeb96e4df54da4647e97c9ce07c8d9e2b2ebe3f0446d5a1fadac6decc69
MD5 5a5ec9f7395754d1ec73292657c46eeb
BLAKE2b-256 a70475a834036addcd399f36e0a5f62c7b582c2cf7150af26a7b20891cb2e95c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page