Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
rev: v0.14.6
hooks:
- id: ruff-check
# - id: ruff-format
- id: ruff-format
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.13
hooks:
Expand All @@ -26,3 +26,10 @@ repos:
hooks:
- id: check-github-workflows
- id: check-renovate
- repo: https://github.com/adamtheturtle/doccmd-pre-commit
rev: v2025.12.8.5
hooks:
- id: doccmd
args: ["--language", "python", "--no-pad-file", "--no-pad-groups", "--command", "ruff format", "docs/"]
additional_dependencies:
- ruff==0.14.8
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- Switch to [Zensical for building the documentation](https://zensical.org/) [#62](https://github.com/python-backoff/backoff/pull/62) (from [@edgarrmondragon](https://github.com/edgarrmondragon))
- Include changelog in the documentation [#65](https://github.com/python-backoff/backoff/pull/65) (from [@edgarrmondragon](https://github.com/edgarrmondragon))

### Internal

- Use Ruff to give the codebase a consistent format [#66](https://github.com/python-backoff/backoff/pull/66) (from [@edgarrmondragon](https://github.com/edgarrmondragon))

## [v2.3.0] - 2025-11-28

### Changed
Expand Down
157 changes: 104 additions & 53 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ is raised. Here's an example using exponential backoff when any

.. code-block:: python

@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException)
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
)
def get_url(url):
return requests.get(url)

Expand All @@ -50,9 +52,13 @@ 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))
@backoff.on_exception(
backoff.expo,
(
requests.exceptions.Timeout,
requests.exceptions.ConnectionError,
),
)
def get_url(url):
return requests.get(url)

Expand All @@ -66,9 +72,11 @@ 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)
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_time=60,
)
def get_url(url):
return requests.get(url)

Expand All @@ -78,10 +86,12 @@ 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)
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_tries=8,
jitter=None,
)
def get_url(url):
return requests.get(url)

Expand All @@ -97,10 +107,13 @@ be retried:
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)

@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_time=300,
giveup=fatal_code,
)
def get_url(url):
return requests.get(url)

Expand All @@ -118,11 +131,14 @@ case, regardless of the logic in the `on_exception` handler.
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)

@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)

Expand All @@ -142,7 +158,11 @@ return value of the target function is the empty list:

.. code-block:: python

@backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13)
@backoff.on_predicate(
backoff.fibo,
lambda x: x == [],
max_value=13,
)
def poll_for_messages(queue):
return queue.get()

Expand All @@ -164,7 +184,11 @@ gets a non-falsey result could be defined like like this:

.. code-block:: python

@backoff.on_predicate(backoff.constant, jitter=None, interval=1)
@backoff.on_predicate(
backoff.constant,
jitter=None,
interval=1,
)
def poll_for_message(queue):
return queue.get()

Expand Down Expand Up @@ -217,12 +241,16 @@ 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)
@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()

Expand All @@ -245,9 +273,13 @@ runtime to obtain the value:
# 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)

@backoff.on_exception(
backoff.expo,
ValueError,
max_time=lookup_max_time,
)
def my_function(): ...

Event handlers
--------------
Expand Down Expand Up @@ -275,13 +307,18 @@ 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))
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)
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
on_backoff=backoff_hdlr,
)
def get_url(url):
return requests.get(url)

Expand All @@ -293,9 +330,14 @@ 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])
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
on_backoff=[
backoff_hdlr1,
backoff_hdlr2,
],
)
def get_url(url):
return requests.get(url)

Expand Down Expand Up @@ -326,7 +368,11 @@ asynchronous HTTP client/server library.

.. code-block:: python

@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)
@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:
Expand All @@ -343,41 +389,46 @@ as:

.. code-block:: python

logging.getLogger('backoff').addHandler(logging.StreamHandler())
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)
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')
# ...
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
logger="my_logger",
)
def my_function(): ...

It is also supported to specify a Logger (or LoggerAdapter) object
directly.

.. code-block:: python

my_logger = logging.getLogger('my_logger')
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)
# ...

@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
logger=my_logger,
)
def my_function(): ...

Default logging can be disabled all together by specifying
``logger=None``. In this case, if desired alternative logging behavior
Expand Down
19 changes: 10 additions & 9 deletions backoff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@
For examples and full documentation see the README at
https://github.com/python-backoff/backoff
"""

from backoff._decorator import on_exception, on_predicate
from backoff._jitter import full_jitter, random_jitter
from backoff._wait_gen import constant, expo, fibo, runtime, decay

__all__ = [
'on_predicate',
'on_exception',
'constant',
'expo',
'decay',
'fibo',
'runtime',
'full_jitter',
'random_jitter',
"on_predicate",
"on_exception",
"constant",
"expo",
"decay",
"fibo",
"runtime",
"full_jitter",
"random_jitter",
]

__version__ = "2.2.1"
Loading
Loading