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


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.9 or greater. For Python 3.10 or greater, it has no external dependencies, i.e. standard library only, but earlier versions require 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.719705 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 pyright.

100% Test Coverage

tubthumper achieves 100% test coverage across three supported operating systems (Windows, MacOS, & Linux). You can find the 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.3.0.tar.gz (620.5 kB view details)

Uploaded Source

Built Distribution

tubthumper-0.3.0-py3-none-any.whl (13.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: tubthumper-0.3.0.tar.gz
  • Upload date:
  • Size: 620.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.4.24

File hashes

Hashes for tubthumper-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3375f400cb089a0c5dc704b39bb9fc48cf1e6bdad09c7450683eb52684302d13
MD5 5787b0f3458d56b669a7ea1196292991
BLAKE2b-256 b9de46064fcb23d2b78e3dbd26702e3ff409d36b6e00ebc36669ba8982f50e0b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tubthumper-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad65ff179e052ba02d79bd44ad0ceeb30d7cd6b56886cb19e7cbe152fe36f170
MD5 451a71ed80739dac8c44e7e794a19b64
BLAKE2b-256 ab26ef860bbfda6ec13f7f2cfabf482445f3b2704d1f892db122725f3ac86fcb

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