diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 416e983..9aba5e4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/docs/examples.md b/docs/examples.md index 5b3c6e0..55d415d 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -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 @@ -79,6 +45,7 @@ def connect_to_database(connection_string): ### Transaction Retry with Deadlock Handling ```python +import backoff from sqlalchemy.exc import DBAPIError @@ -111,6 +78,7 @@ def execute_transaction(session, operation): ```python import aiohttp + import backoff @@ -128,6 +96,7 @@ async def fetch_async(url): ```python import asyncpg +import backoff @backoff.on_exception( @@ -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, @@ -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", diff --git a/docs/getting-started.md b/docs/getting-started.md index 62a5722..2c2488a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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, diff --git a/docs/index.md b/docs/index.md index ed64404..33a41dc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,9 +29,10 @@ pip install python-backoff Basic retry on exception: ```python -import backoff import requests +import backoff + @backoff.on_exception( backoff.expo, diff --git a/docs/user-guide/async.md b/docs/user-guide/async.md index db1f564..b3e62a7 100644 --- a/docs/user-guide/async.md +++ b/docs/user-guide/async.md @@ -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): @@ -142,9 +143,11 @@ async def async_function(): ```python import asyncio +import logging + import aiohttp + import backoff -import logging logger = logging.getLogger(__name__) diff --git a/docs/user-guide/decorators.md b/docs/user-guide/decorators.md index 02d72bb..d4938bd 100644 --- a/docs/user-guide/decorators.md +++ b/docs/user-guide/decorators.md @@ -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, diff --git a/docs/user-guide/event-handlers.md b/docs/user-guide/event-handlers.md index 24a2b8b..e5ba694 100644 --- a/docs/user-guide/event-handlers.md +++ b/docs/user-guide/event-handlers.md @@ -170,8 +170,8 @@ def my_function(): ### Structured Logging ```python -import logging import json +import logging logger = logging.getLogger(__name__) diff --git a/docs/user-guide/logging.md b/docs/user-guide/logging.md index c2c9502..c17c732 100644 --- a/docs/user-guide/logging.md +++ b/docs/user-guide/logging.md @@ -111,8 +111,8 @@ logging.getLogger("backoff").addHandler(logging.StreamHandler()) ### Structured Logging (JSON) ```python -import logging import json +import logging class JsonFormatter(logging.Formatter): diff --git a/mkdocs.yml b/mkdocs.yml index 25232d8..0803908 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,8 +30,7 @@ markdown_extensions: - pymdownx.highlight - pymdownx.superfences - pymdownx.inlinehilite - - pymdownx.snippets: - base_path: ['.'] + - pymdownx.snippets - admonition plugins: - search diff --git a/pyproject.toml b/pyproject.toml index 1d41e5d..9d5cc08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" }, @@ -143,6 +145,7 @@ commands = [ "--show-error-codes", { replace = "posargs", default = [ "backoff", + "snippets", "tests", ], extend = true }, ], diff --git a/snippets/examples/001_basic.py b/snippets/examples/001_basic.py new file mode 100644 index 0000000..7c5e35d --- /dev/null +++ b/snippets/examples/001_basic.py @@ -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() diff --git a/snippets/examples/002_retry_after.py b/snippets/examples/002_retry_after.py new file mode 100644 index 0000000..5cc6e7b --- /dev/null +++ b/snippets/examples/002_retry_after.py @@ -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) diff --git a/snippets/examples/003_status_code.py b/snippets/examples/003_status_code.py new file mode 100644 index 0000000..da1abb6 --- /dev/null +++ b/snippets/examples/003_status_code.py @@ -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 diff --git a/snippets/ruff.toml b/snippets/ruff.toml new file mode 100644 index 0000000..4055e5f --- /dev/null +++ b/snippets/ruff.toml @@ -0,0 +1,2 @@ +[lint.isort] +detect-same-package = false