Relinker — a clear, modular, and debuggable retry/resilience library for Python.
Project description
Relinker
Simple by default, powerful by composition, safe by guidance.
Relinker is a clear, modular, and debuggable retry library for Python.
It helps you retry temporary failures without hiding what your code is doing.
Overview · Features · Quick Start · Guidance · HTTP · Observability · Examples · Documentation
Overview
Applications fail for reasons that are often temporary:
- a network timeout
- a busy API
- a database connection hiccup
- a rate limit response
- a background job that should be tried again
Relinker gives you a clean way to describe what should happen:
from relinker import RetryPolicy
policy = (
RetryPolicy()
.attempts(5)
.on(TimeoutError, ConnectionError)
.exponential_delay(base=1, maximum=30)
.jitter(maximum=0.5)
)
This reads like a sentence:
Try up to 5 times, retry only on timeout or connection errors, wait with exponential backoff, and add jitter.
Features
Relinker currently provides:
- simple
@retrydecorator - fluent
RetryPolicybuilder - shared, process-local
RetryBudgetprotection against retry storms - sync and async execution
- retry by exception, returned result, or custom condition
- fixed, linear, chain, exponential, random, randomized exponential, custom, additive, and state-aware delays
- composable stop strategies
- composable retry conditions
- built-in presets for common situations
- rich
RetryResultobjects - per-function retry statistics
warnings()for risky policiesdoctor()health reportssimulate(),timeline(),preview(), andexplain()- safe structured logging
- event hooks for observability
- HTTP retry helpers with
Retry-Aftersupport - context manager support for retrying blocks
- testing support through custom sleep functions
- zero required runtime dependencies
Current status
Relinker 1.0 provides a stable public API for Python 3.10 through 3.13.
Compatibility guarantees apply to the documented exports and behaviors described
in the compatibility policy. Release history lives in
CHANGELOG.md.
See the migration guide if you are upgrading from an earlier version.
Install from PyPI:
pip install relinker
For development or to track the latest changes on the main branch:
pip install git+https://github.com/igors93/relinker.git
For local development:
git clone https://github.com/igors93/relinker.git
cd relinker
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Quick Start
The smallest useful retry
from relinker import retry
@retry(attempts=3, delay=1, on=(TimeoutError,))
def fetch_data() -> str:
return call_external_service()
If fetch_data() raises TimeoutError, Relinker tries again up to 3 times and waits 1 second between attempts.
Use a preset
from relinker import network
@network()
def call_api() -> dict:
return client.get("/users/1")
Presets are regular policies. You can keep customizing them:
policy = network().attempts(8).fallback_value({"status": "offline"})
Share a retry budget
A retry budget limits additional attempts across executions that share the same budget object and key. The original attempt is never counted.
from relinker import RetryBudget, RetryPolicy
budget = RetryBudget(max_retries=20, per=60)
policy = (
RetryPolicy()
.attempts(5)
.on(TimeoutError)
.exponential_delay(base=1, maximum=30)
.with_retry_budget(budget, key="external-api")
)
RetryBudget is in-memory and process-local. Separate processes do not share
capacity. Normal policy delays and max_time() continue to apply. See
Retry budgets for the complete behavior and scope.
Use the full builder
from relinker import RetryPolicy
policy = (
RetryPolicy()
.attempts(5)
.on(TimeoutError, ConnectionError)
.exponential_delay(base=1, maximum=30)
.jitter(maximum=0.5)
.fallback_value({"status": "unavailable"})
)
result = policy.run(fetch_data)
Guidance
Relinker does not try to control your application. It lets you make your own choices, but it helps you notice risky retry policies.
from relinker import RetryPolicy
policy = RetryPolicy().forever().on(Exception).no_delay()
print(policy.doctor().describe())
Example output:
Relinker policy health
Risk level: risky
Warnings:
- forever: This policy can retry forever.
- no_delay: This policy has no delay between attempts.
- tight_loop_risk: This policy can retry forever without sleeping.
- broad_exception: This policy retries all Exception subclasses.
Use explain() when you want to understand a policy in plain language:
print(policy.explain())
Use preview() when you want to estimate timing before running real code:
print(policy.preview(attempts=5))
HTTP retry
Relinker includes dependency-free HTTP helpers. They work with any response object that exposes .status_code or a dictionary with a "status_code" key.
from relinker import http_retry_policy
policy = http_retry_policy(
attempts=5,
statuses={429, 500, 502, 503, 504},
respect_retry_after=True,
)
For lower-level control:
from relinker import RetryPolicy, retry_after_delay, retry_if_status
policy = (
RetryPolicy()
.attempts(5)
.retry_if_result(retry_if_status({429, 500, 502, 503, 504}))
.stateful_delay(retry_after_delay(default=1.0, maximum=60.0))
)
This is useful for APIs that return 429 Too Many Requests with a Retry-After header.
Observability
Human-readable logging
import logging
from relinker import RetryPolicy
logging.basicConfig(level=logging.INFO)
policy = RetryPolicy().attempts(3).on(TimeoutError).with_logging(level=logging.INFO)
Structured logging
policy = RetryPolicy().attempts(3).on(TimeoutError).with_structured_logging()
Structured logs exclude error messages by default because exception messages can contain tokens, URLs, payload fragments, or user data.
Events
from relinker import RetryEvent, RetryPolicy
def on_retry(event: RetryEvent) -> None:
print(f"retrying after attempt {event.attempt_number}, delay={event.delay}")
policy = RetryPolicy().attempts(3).on(TimeoutError).on_retry(on_retry)
Results and statistics
Return a RetryResult when you want full visibility:
result = RetryPolicy().attempts(3).return_result().run(fetch_data)
print(result.summary())
print(result.story())
Decorated functions also receive retry statistics:
from relinker import network
@network()
def fetch_user() -> dict:
return {"id": 1}
fetch_user()
print(fetch_user.retry_stats.to_dict())
Examples
Run examples from the project root:
python -m examples.basic_retry
python -m examples.retry_with_policy
python -m examples.retry_policy_doctor
python -m examples.retry_preview_and_explain
python -m examples.retry_http_retry_after
python -m examples.retry_structured_logging
See examples/README.md for the full list.
Documentation
The documentation is organized by topic:
- Documentation index
- Getting started
- Retry lifecycle
- Exhaustion behavior
- Policy builder
- Retry budgets
- Diagnostics and guidance
- HTTP retry
- Observability
- Results and statistics
- Context manager usage
- Testing retry code
- API reference
- Compatibility policy
- Architecture
- Production checklist
Development checks
./scripts/ci.sh
Or run each step:
python -m ruff format --check .
python -m ruff check .
python -m mypy src
python -m pytest
python -m build
Design principles
Relinker is guided by these principles:
- Simple things should be simple.
- Advanced things should be possible.
- Code should be readable and modular.
- Names should be intuitive.
- Defaults should be safe.
- Dangerous policies should produce warnings.
- The user stays in control.
- Debugging should be built in.
- No unnecessary magic.
- Production behavior should be explainable.
License
MIT.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file relinker-1.0.1.tar.gz.
File metadata
- Download URL: relinker-1.0.1.tar.gz
- Upload date:
- Size: 123.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58179f83c275492b6b13da9010a50c2fd1a19932cec8e95e5748d37e50a4403e
|
|
| MD5 |
b7ddf9fc17dea0de8ca75eda9379adfb
|
|
| BLAKE2b-256 |
de5d737a3cd45dae155e8504626aeded7d57207675507bb43443c52fa8d0a935
|
Provenance
The following attestation bundles were made for relinker-1.0.1.tar.gz:
Publisher:
publish.yml on igors93/relinker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
relinker-1.0.1.tar.gz -
Subject digest:
58179f83c275492b6b13da9010a50c2fd1a19932cec8e95e5748d37e50a4403e - Sigstore transparency entry: 1740749743
- Sigstore integration time:
-
Permalink:
igors93/relinker@6b59e8732c9d419d11bee29110b69dfeaeb47405 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/igors93
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6b59e8732c9d419d11bee29110b69dfeaeb47405 -
Trigger Event:
release
-
Statement type:
File details
Details for the file relinker-1.0.1-py3-none-any.whl.
File metadata
- Download URL: relinker-1.0.1-py3-none-any.whl
- Upload date:
- Size: 64.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c581e12ba7f65a7ba7a5ba4a7e0bbffc36d5124316c6f432565675b7b67fc588
|
|
| MD5 |
c3aafb73daceb892bd8a73a219fb453a
|
|
| BLAKE2b-256 |
b042a2e433f275143e7a6c695487783b8fee5c0d3071230de3c7bc9f7451d0e8
|
Provenance
The following attestation bundles were made for relinker-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on igors93/relinker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
relinker-1.0.1-py3-none-any.whl -
Subject digest:
c581e12ba7f65a7ba7a5ba4a7e0bbffc36d5124316c6f432565675b7b67fc588 - Sigstore transparency entry: 1740749762
- Sigstore integration time:
-
Permalink:
igors93/relinker@6b59e8732c9d419d11bee29110b69dfeaeb47405 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/igors93
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6b59e8732c9d419d11bee29110b69dfeaeb47405 -
Trigger Event:
release
-
Statement type: