{"id":17897311,"url":"https://github.com/matteosox/tubthumper","last_synced_at":"2025-07-27T21:40:32.564Z","repository":{"id":37073561,"uuid":"413668247","full_name":"matteosox/tubthumper","owner":"matteosox","description":"Python package of retry utilities named after the English anarcho-communist rock band Chumbawamba's 1997 hit Tubthumping","archived":false,"fork":false,"pushed_at":"2025-01-04T00:07:19.000Z","size":843,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-26T16:50:48.477Z","etag":null,"topics":["exponential-backoff","jitter","python","retry"],"latest_commit_sha":null,"homepage":"https://tubthumper.mattefay.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matteosox.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-05T04:04:54.000Z","updated_at":"2025-01-04T00:07:22.000Z","dependencies_parsed_at":"2024-10-28T15:27:49.451Z","dependency_job_id":"9cb34838-fae4-4e1f-8706-2762c121453f","html_url":"https://github.com/matteosox/tubthumper","commit_stats":{"total_commits":37,"total_committers":2,"mean_commits":18.5,"dds":"0.027027027027026973","last_synced_commit":"aaae3ea347d004b84e8bbd6588eb8dae36affbf6"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/matteosox/tubthumper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matteosox%2Ftubthumper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matteosox%2Ftubthumper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matteosox%2Ftubthumper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matteosox%2Ftubthumper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matteosox","download_url":"https://codeload.github.com/matteosox/tubthumper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matteosox%2Ftubthumper/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267429566,"owners_count":24085974,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-27T02:00:11.917Z","response_time":82,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exponential-backoff","jitter","python","retry"],"created_at":"2024-10-28T15:08:47.134Z","updated_at":"2025-07-27T21:40:32.527Z","avatar_url":"https://github.com/matteosox.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **Tubthumper**: Helping you get up ... again!\n\n[![CI/CD: n/a](https://github.com/matteosox/tubthumper/actions/workflows/cicd.yaml/badge.svg)](https://github.com/matteosox/tubthumper/actions/workflows/cicd.yaml)\n[![Docs: n/a](https://readthedocs.org/projects/tubthumper/badge/?version=stable)](https://tubthumper.mattefay.com)\n[![Downloads: n/a](https://static.pepy.tech/personalized-badge/tubthumper?period=total\u0026units=none\u0026left_color=grey\u0026right_color=blue\u0026left_text=Downloads)](https://pepy.tech/project/tubthumper)\n[![PyPI: n/a](https://img.shields.io/badge/dynamic/json?color=blueviolet\u0026label=PyPI\u0026query=%24.info.version\u0026url=https%3A%2F%2Fpypi.org%2Fpypi%2Ftubthumper%2Fjson)](https://pypi.org/project/tubthumper/)\n[![codecov: n/a](https://codecov.io/gh/matteosox/tubthumper/branch/main/graph/badge.svg?token=8VKKDG9SMZ)](https://codecov.io/gh/matteosox/tubthumper)\n\n----\n\n## What's in a name?\n\n**Tubthumper** is a Python package of retry utilities named after the English anarcho-communist rock band Chumbawamba's 1997 hit [Tubthumping](https://www.youtube.com/watch?v=2H5uWRjFsGc). Yes, really.\n\n\u003e I get knocked down, but I get up again. 🎶\\\n\u003e You're never gonna keep me down. 🎶\\\n\u003e I get knocked down, but I get up again. 🎶\\\n\u003e You're never gonna keep me down... 🎶\n\n## Getting Started\n\n### Installation\n\n`tubthumper` is a pip-installable package [hosted on PyPI](https://pypi.org/project/tubthumper/). Getting started is as easy as:\n\n```console\n$ pip install tubthumper\n```\n\n`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`](https://pypi.org/project/typing-extensions/).\n\n### Usage\n\nImport `tubthumper`'s useful bits:\n```python\n\u003e\u003e\u003e from tubthumper import retry, retry_decorator, retry_factory\n```\n\nCall a function with retry and jittered exponential backoff:\n```python\n\u003e\u003e\u003e retry(get_ip, exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 0.844422 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nCall that same function with positional and keyword arguments, e.g. retry `get_ip(42, \"test\", dev=True)`:\n```python\n\u003e\u003e\u003e retry(get_ip,\n...     args=(42, \"test\"), kwargs={\"dev\": True},\n...     exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 0.420572 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nBake retry behavior into your function with a decorator:\n```python\n\u003e\u003e\u003e @retry_decorator(exceptions=ConnectionError)\n... def get_ip_retry():\n...     return requests.get(\"http://ip.jsontest.com\").json()\n\u003e\u003e\u003e get_ip_retry()\nWARNING: Function threw exception below on try 1, retrying in 0.511275 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nCreate a new function with retry behavior from an existing one:\n```python\n\u003e\u003e\u003e get_ip_retry = retry_factory(get_ip, exceptions=ConnectionError)\n\u003e\u003e\u003e get_ip_retry()\nWARNING: Function threw exception below on try 1, retrying in 0.783799 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\n## Customization\n\nWhile `tubthumper` ships with a set of sensible defaults, its retry behavior is fully customizable.\n\n### Exceptions\n\nBecause overbroad except clauses are [the most diabolical Python antipattern](https://realpython.com/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.\n\n```python\n\u003e\u003e\u003e retry(get_ip, exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 0.476597 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n\u003e\u003e\u003e retry(get_ip, exceptions=(KeyError, ConnectionError))\nWARNING: Function threw exception below on try 1, retrying in 0.908113 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nBy default, `tubthumper` raises a `tubthumper.RetryError` exception when all retries have been exhausted:\n\n```python\n\u003e\u003e\u003e retry(lambda: 1/0, retry_limit=0, exceptions=ZeroDivisionError)\nTraceback (most recent call last):\n  ...\ntubthumper._retry_factory.RetryError: Retry limit 0 reached\n```\n\nYou can override this behavior using the `reraise` flag to reraise the original exception in place of `RetryError`:\n\n```python\n\u003e\u003e\u003e retry(lambda: 1/0, retry_limit=0, reraise=True, exceptions=ZeroDivisionError)\nTraceback (most recent call last):\n  ...\nZeroDivisionError: division by zero\n```\n\n### Retry Limits\n\nBy 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:\n\n```python\n\u003e\u003e\u003e retry(lambda: 1/0, retry_limit=10, exceptions=ZeroDivisionError)\n...  # Warning logs for each failed call\nTraceback (most recent call last):\n  ...\ntubthumper._retry_factory.RetryError: Retry limit 10 reached\n```\n\nAlternatively, you can use the `time_limit` keyword-only argument to prevent retry attempts after a certain duration:\n\n```python\n\u003e\u003e\u003e retry(lambda: 1/0, time_limit=60, exceptions=ZeroDivisionError)\n...  # Warning logs for each failed call\nTraceback (most recent call last):\n  ...\ntubthumper._retry_factory.RetryError: Time limit 60 exceeded\n```\n\n### Backoff timing\n\nBy 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:\n\n```python\n\u003e\u003e\u003e retry(get_ip, jitter=False, exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 1 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nYou can set the initial backoff duration using the `init_backoff` keyword-only argument:\n\n```python\n\u003e\u003e\u003e retry(get_ip, jitter=False, init_backoff=10, exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 10 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\nFinally, you can set the factor by which each successive backoff duration is scaled using the `exponential` keyword-only argument:\n\n```python\n\u003e\u003e\u003e retry(get_ip, jitter=False, exponential=3, exceptions=ConnectionError)\nWARNING: Function threw exception below on try 1, retrying in 1 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\nWARNING: Function threw exception below on try 2, retrying in 3 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\n### Logging\n\nBy 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](https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library), for this default logger, \"events of severity WARNING and greater will be printed to sys.stderr\" if no further logging is configured.\n\nYou can set the logging level using the `log_level` keyword-only argument:\n\n```python\n\u003e\u003e\u003e retry(get_ip, log_level=logging.DEBUG, exceptions=ConnectionError) # No warnings\n{'ip': '8.8.8.8'}\n```\n\nYou can provide your own logger using the `logger` keyword-only argument. This logger's `log` method will be called like so:\n\n```python\nlogger.log(log_level, \"Function threw...\", exc_info=True)\n```\n\n## Features\n\n### Compatible with methods\n\n`tubthumper`'s various interfaces are compatible with methods, including classmethods and staticmethods:\n\n```python\n\u003e\u003e\u003e class Class:\n...     @retry_decorator(exceptions=ConnectionError)\n...     def get_ip(self):\n...         return requests.get(\"http://ip.jsontest.com\").json()\n...\n\u003e\u003e\u003e Class().get_ip()\nWARNING: Function threw exception below on try 1, retrying in 0.719705 seconds\nTraceback (most recent call last):\n  ...\nrequests.exceptions.ConnectionError: http://ip.jsontest.com\n{'ip': '8.8.8.8'}\n```\n\n### Signature preserving\n\n`tubthumper`'s various interfaces preserve the relevant [dunder](https://wiki.python.org/moin/DunderAlias) attributes of your function:\n\n```python\n\u003e\u003e\u003e @retry_decorator(exceptions=ConnectionError)\n... def func(one: bool, two: float = 3.0) -\u003e complex:\n...     \"\"\"This is a docstring\"\"\"\n...\n\u003e\u003e\u003e func.__name__\n'func'\n\u003e\u003e\u003e func.__qualname__\n'func'\n\u003e\u003e\u003e func.__module__\n'__main__'\n\u003e\u003e\u003e func.__doc__\n'This is a docstring'\n\u003e\u003e\u003e func.__annotations__\n{'one': \u003cclass 'bool'\u003e, 'two': \u003cclass 'float'\u003e, 'return': \u003cclass 'complex'\u003e}\n```\n\n`tubthumper` also preserves the inspect module's function signature, and `is*` functions:\n\n```python\n\u003e\u003e\u003e import inspect\n\u003e\u003e\u003e inspect.signature(func)\n\u003cSignature (one: bool, two: float = 3.0) -\u003e complex\u003e\n\u003e\u003e\u003e inspect.isfunction(func)\nTrue\n\u003e\u003e\u003e inspect.isroutine(func)\nTrue\n\u003e\u003e\u003e inspect.ismethod(Class().get_ip)\nTrue\n```\n\n### Async support\n\n`tubthumper`'s various interfaces support coroutine functions, including [generator-based coroutines](https://docs.python.org/3/library/asyncio-task.html#generator-based-coroutines), awaiting them while using `async.sleep` between awaits:\n\n```python\n\u003e\u003e\u003e @retry_decorator(exceptions=ConnectionError)\n... async def get_ip():\n...     return requests.get(\"http://ip.jsontest.com\").json()\n...\n\u003e\u003e\u003e inspect.iscoroutinefunction(get_ip)\nTrue\n```\n\n### Fully type annotated\n\n`tubthumper`'s various interfaces are fully type annotated, passing [pyright](https://microsoft.github.io/pyright/#/).\n\n### 100% Test Coverage\n\n`tubthumper` achieves 100% test coverage across three supported operating systems (Windows, MacOS, \u0026 Linux). You can find the coverage report on [Codecov](https://codecov.io/gh/matteosox/tubthumper).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatteosox%2Ftubthumper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatteosox%2Ftubthumper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatteosox%2Ftubthumper/lists"}