Skip to main content

🌿 tranq

Calm, production-grade error handling & resilience for Python — decorator-based, zero boilerplate.

PyPI GitHub Python 3.9+ Tests License ![CI](https://github.com/RaptorVampire/tranq/actions/workflows/ci.yml/badge.svg) ![PyPI](https://img.shields.io/pypi/v/tranq) ![Python](https://img.shields.io/pypi/pyversions/tranq)


🧘 Why tranq?

Writing repetitive try/except blocks clutters your code and hides business logic. tranq gives you declarative error handling and a full resilience toolkit — retries, circuit breakers, rate limits, bulkheads, hedging, timeouts, budgets, metrics and more — so you focus on what your code does, not how it recovers from failure.

Feature Description
🔁 Smart retries Exponential / linear / Fibonacci backoff, jitter, max delay
⏱️ Timeouts Hard execution limits for sync & async functions
🚦 Circuit Breaker Count-based and sliding-window (failure-rate), sync & async
🚦 Rate Limiter Token-bucket throttling (@rate_limit)
🚪 Bulkhead Concurrency isolation (@bulkhead)
🏇 Hedged requests Race duplicate async requests, take the first success
💰 Retry budget Prevent retry storms under load
🪝 Event hooks on_retry / on_success / on_failure / on_complete
📊 Metrics Counts, error-rate and p50 / p95 / p99 latencies
📈 Statistics Summary tables & health reports
📝 Reporters File (JSON), Log, Sentry, Slack, Prometheus
🧩 Context managers tranq.retry(...) and async with tranq.retry_async(...)
📦 Retry groups All-or-nothing execution for multiple functions
🔧 Stateful retry Persist attempt count across calls (thread/async safe)
🎭 Mock errors Inject exceptions for testing
💉 Dependency injection Inject dependencies into decorated functions
🌐 Global policy Set defaults once, override per function

📦 Installation

pip install tranq

Optional integrations:

pip install tranq[rich]         # pretty logging
pip install tranq[sentry]       # Sentry reporter
pip install tranq[slack]        # Slack reporter
pip install tranq[prometheus]   # Prometheus reporter
pip install tranq[all]          # everything

⚡ Quick Start

import tranq

@tranq.handle(on=ConnectionError, retry=3, delay=0.5, backoff=2.0, timeout=5.0)
def fetch():
    ...

@tranq.handle_async(on=TimeoutError, retry=2, fallback=lambda: "offline")
async def fetch_async():
    ...

🔍 Features in Depth

Retry with Backoff

@tranq.handle(on=TimeoutError, retry=5, delay=0.1, backoff=2.0,
              backoff_strategy="exponential", max_delay=10.0, jitter=True)
def fetch(): ...

Timeout

@tranq.handle(on=Exception, retry=2, timeout=3.0)   # raises FunctionTimeoutError
def slow(): ...

Event Hooks

@tranq.handle(on=ValueError, retry=3,
              on_retry=lambda e, n: print(f"retry {n}"),
              on_success=lambda r: print("ok"),
              on_failure=lambda e: print("failed"),
              on_complete=lambda: print("done"))
def work(): ...

Circuit Breaker (count-based & sliding-window)

cb  = tranq.CircuitBreaker(failure_threshold=5, timeout=60)
swc = tranq.SlidingWindowCircuitBreaker(window_size=100,
                                        failure_rate_threshold=0.5,
                                        minimum_calls=10)

@tranq.handle(circuit_breaker=swc)
def call_service(): ...

Rate Limiter

@tranq.rate_limit(rate=10, per=1.0, burst=20)
def api_call(): ...

Bulkhead

@tranq.bulkhead(max_concurrent=5, timeout=1.0)
def limited(): ...

Retry Budget

budget = tranq.RetryBudget(ttl=60, ratio=0.2, min_tokens=10)

@tranq.handle(on=Exception, retry=5, retry_budget=budget)
def protected(): ...

Hedged Requests (async)

@tranq.hedged(hedge_delay=0.1, max_hedges=2)
async def fast_fetch(): ...

Async Context Manager

async with tranq.retry_async(on=ConnectionError, retry=3) as ctx:
    result = await ctx.run(my_async_func, arg)

Metrics & Statistics

@tranq.handle(metrics=True, metric_prefix="svc")
def op(): ...

print(tranq.summary_table())      # aligned table with p50/p95/p99
print(tranq.overall_health())     # {'status': 'healthy', ...}

Reporters

reporters = [
    tranq.FileReporter("errors.jsonl"),
    tranq.LogReporter(),
    tranq.SentryReporter(dsn="..."),
    tranq.SlackReporter(webhook_url="..."),
    tranq.PrometheusReporter(),
]

@tranq.handle(on=Exception, reporters=reporters)
def critical(): ...

📚 API Reference

Decorators

  • tranq.handle(on, retry, delay, backoff, backoff_strategy, max_delay, jitter, fallback, reraise, log_level, message, policy, retry_if, retry_on_result, on_error, metrics, metric_prefix, circuit_breaker, stateful, reporters, inject, timeout, on_retry, on_success, on_failure, on_complete, retry_budget)
  • tranq.handle_async(...) — same parameters for async def.

Standalone decorators

  • tranq.rate_limit(rate, per=1.0, burst=None, timeout=None, raise_on_limit=True)
  • tranq.bulkhead(max_concurrent, timeout=None, raise_on_full=True)
  • tranq.hedged(hedge_delay=0.1, max_hedges=2)
  • tranq.profile / tranq.async_profile

Context managers

  • tranq.retry(...)ctx.run(func, *args, **kwargs)
  • tranq.retry_async(...)async with + await ctx.run(...)

Circuit breakers

  • tranq.CircuitBreaker(failure_threshold, timeout, half_open_requests) + .reset()
  • tranq.AsyncCircuitBreaker(...) + .reset()
  • tranq.SlidingWindowCircuitBreaker(window_size, failure_rate_threshold, timeout, half_open_requests, minimum_calls)
  • tranq.AsyncSlidingWindowCircuitBreaker(...)

Resilience primitives

  • tranq.RateLimiter(rate, per, burst) / .acquire() / .acquire_async()
  • tranq.Bulkhead(max_concurrent, timeout) / tranq.AsyncBulkhead(...)
  • tranq.RetryBudget(ttl, ratio, min_tokens) / .allow_retry() / .record_call()
  • tranq.hedged_call(func, args, kwargs, hedge_delay, max_hedges)

Metrics & statistics

  • tranq.get_metrics() → count, errors, total/avg/min/max, error_rate, p50/p95/p99
  • tranq.reset_metrics()
  • tranq.summary_table() / tranq.overall_health() / tranq.render_report()
  • tranq.get_profile(name=None)

Global policy

  • tranq.set_global_policy(tranq.Policy(...)) / tranq.get_global_policy()

Exceptions

Exception Meaning
TranqError Base class
RetryExhaustedError Retries exhausted
CircuitBreakerError Circuit open
ResultNotAcceptedError retry_on_result rejected final result
RetryGroupError Retry group member failed
FunctionTimeoutError timeout exceeded (also a builtin TimeoutError)
RateLimitExceeded Rate limiter rejected the call
BulkheadFullError Bulkhead at capacity
RetryBudgetExhaustedError Retry budget empty

📁 Examples

The examples/ directory contains 29 runnable examples (01–29) covering every feature, including the new ones: timeouts, hooks, rate limiting, bulkheads, retry budgets, hedging, sliding-window breakers, statistics and retry_async.

python examples/run_all.py

🧪 Testing

The suite contains 135+ tests (sync, async, thread-safety).

pip install pytest "pytest-asyncio>=0.24"
pytest tests/ -v

⚖️ Comparison

Feature tranq tenacity backoff
Decorator + Async
Circuit Breaker
Sliding-window breaker
Rate Limiter
Bulkhead
Retry Budget
Hedged Requests
Timeouts ⚠️
Event Hooks ⚠️
Context Manager
Retry Groups
Metrics + percentiles
Reporters

🤝 Contributing

Contributions are welcome! See CONTRIBUTING.md.

📄 License

MIT © RaptorVampire

Release files for tranq 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tranq 1.0.0
File Size Uploaded
tranq-1.0.0.tar.gz 45.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tranq 1.0.0
File Interpreter ABI Platform
tranq-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 73.6 kB

Release files / tranq-1.0.0.tar.gz

Download URL tranq-1.0.0.tar.gz
Size 45.7 kB
Tags Source
SHA-256 checksum
How to use checksums
0f42db3cf4ad18ed30d4deba88101228aa2f91c201f0015c2c34abc4ff1f17cf
BLAKE2b-256 checksum
How to use checksums
8229984c922bde5e594d14502212b0d99cec96db901be1eaa6943b8cc7566b1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / tranq-1.0.0-py3-none-any.whl

Download URL tranq-1.0.0-py3-none-any.whl
Size 27.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
930b525160137fd3f0bf9e4a0c7fa63598d1c60075799f94a0c4ce8809fb0b09
BLAKE2b-256 checksum
How to use checksums
5812933d4904db8d1589089cca4ede3bf5d473f98cd96c3b9f8f218c2851d3ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

1.1.0

2 release files

This release

1.0.0 This release

2 release files

0.3.0

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page