Skip to content
Draft
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
15 changes: 0 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,3 @@ repos:
hooks:
- id: check-github-workflows
- id: check-renovate
- repo: https://github.com/adamtheturtle/doccmd-pre-commit
rev: v2026.1.18
hooks:
- id: doccmd
name: Ruff format docs
alias: ruff-format-docs
args: ["--language", "python", "--no-pad-file", "--no-pad-groups", "--command", "ruff format", "docs/"]
additional_dependencies:
- ruff==0.14.8
- id: doccmd
name: Ruff check fix docs
alias: ruff-check-fix-docs
args: ["--language", "python", "--no-pad-file", "--no-pad-groups", "--command", "ruff check --fix", "docs/"]
additional_dependencies:
- ruff==0.14.8
53 changes: 16 additions & 37 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,19 @@ Real-world examples of using backoff in production.
### Basic API Retry

```python
import backoff
import requests


@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_time=60,
)
def fetch_data(url):
response = requests.get(url)
response.raise_for_status()
return response.json()
--8<-- "snippets/examples/001_basic.py"
```

### Rate Limiting with Retry-After

```python
@backoff.on_predicate(
backoff.runtime,
predicate=lambda r: r.status_code == 429,
value=lambda r: int(r.headers.get("Retry-After", 1)),
jitter=None,
max_tries=10,
)
def rate_limited_api_call(endpoint):
return requests.get(endpoint)
--8<-- "snippets/examples/002_retry_after.py"
```

### Conditional Retry on Status Codes

```python
def should_retry(response):
# Retry on 5xx and 429, but not 4xx
return response.status_code >= 500 or response.status_code == 429


@backoff.on_predicate(
backoff.expo,
should_retry,
max_time=120,
)
def resilient_api_call(url):
response = requests.get(url)
if 400 <= response.status_code < 500 and response.status_code != 429:
response.raise_for_status() # Don't retry client errors
return response
--8<-- "snippets/examples/003_status_code.py:5"
```

## Database Operations
Expand All @@ -79,6 +45,7 @@ def connect_to_database(connection_string):
### Transaction Retry with Deadlock Handling

```python
import backoff
from sqlalchemy.exc import DBAPIError


Expand Down Expand Up @@ -111,6 +78,7 @@ def execute_transaction(session, operation):

```python
import aiohttp

import backoff


Expand All @@ -128,6 +96,7 @@ async def fetch_async(url):

```python
import asyncpg
import backoff


@backoff.on_exception(
Expand All @@ -143,6 +112,12 @@ async def query_async(pool, query):
### Multiple Async Tasks with Individual Retries

```python
import asyncio

import aiohttp
import backoff


@backoff.on_exception(
backoff.expo,
aiohttp.ClientError,
Expand All @@ -164,6 +139,10 @@ async def fetch_all(urls):
### Poll for Job Completion

```python
import backoff
import requests


@backoff.on_predicate(
backoff.constant,
lambda job: job["status"] != "complete",
Expand Down
3 changes: 2 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ Backoff provides two main decorators:
Let's start with a simple example - retrying a network request:

```python
import backoff
import requests

import backoff


@backoff.on_exception(
backoff.expo,
Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ pip install python-backoff
Basic retry on exception:

```python
import backoff
import requests

import backoff


@backoff.on_exception(
backoff.expo,
Expand Down
7 changes: 5 additions & 2 deletions docs/user-guide/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ Backoff fully supports Python's `async`/`await` syntax for asynchronous code.
Simply decorate async functions with the same decorators:

```python
import backoff
import aiohttp

import backoff


@backoff.on_exception(backoff.expo, aiohttp.ClientError)
async def fetch_data(url):
Expand Down Expand Up @@ -142,9 +143,11 @@ async def async_function():

```python
import asyncio
import logging

import aiohttp

import backoff
import logging

logger = logging.getLogger(__name__)

Expand Down
3 changes: 2 additions & 1 deletion docs/user-guide/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ The `on_exception` decorator retries a function when a specified exception is ra
### Basic Usage

```python
import backoff
import requests

import backoff


@backoff.on_exception(
backoff.expo,
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/event-handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ def my_function():
### Structured Logging

```python
import logging
import json
import logging

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ logging.getLogger("backoff").addHandler(logging.StreamHandler())
### Structured Logging (JSON)

```python
import logging
import json
import logging


class JsonFormatter(logging.Formatter):
Expand Down
3 changes: 1 addition & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ markdown_extensions:
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.inlinehilite
- pymdownx.snippets:
base_path: ['.']
- pymdownx.snippets
- admonition
plugins:
- search
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Repository = "https://github.com/python-backoff/backoff"

[dependency-groups]
dev = [
"requests",
"sqlalchemy",
{ include-group = "docs" },
{ include-group = "lint" },
{ include-group = "test" },
Expand Down Expand Up @@ -143,6 +145,7 @@ commands = [
"--show-error-codes",
{ replace = "posargs", default = [
"backoff",
"snippets",
"tests",
], extend = true },
],
Expand Down
13 changes: 13 additions & 0 deletions snippets/examples/001_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import backoff
import requests


@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_time=60,
)
def fetch_data(url):
response = requests.get(url)
response.raise_for_status()
return response.json()
13 changes: 13 additions & 0 deletions snippets/examples/002_retry_after.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import backoff
import requests


@backoff.on_predicate(
backoff.runtime,
predicate=lambda r: r.status_code == 429,
value=lambda r: int(r.headers.get("Retry-After", 1)),
jitter=None,
max_tries=10,
)
def rate_limited_api_call(endpoint):
return requests.get(endpoint)
19 changes: 19 additions & 0 deletions snippets/examples/003_status_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import backoff
import requests


def should_retry(response):
# Retry on 5xx and 429, but not 4xx
return response.status_code >= 500 or response.status_code == 429


@backoff.on_predicate(
backoff.expo,
should_retry,
max_time=120,
)
def resilient_api_call(url):
response = requests.get(url)
if 400 <= response.status_code < 500 and response.status_code != 429:
response.raise_for_status() # Don't retry client errors
return response
2 changes: 2 additions & 0 deletions snippets/ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[lint.isort]
detect-same-package = false
Loading