Modern, decorator-based error handling for Python – calm your code.
Project description
tranq
Calm error handling for Python – decorator-based, zero boilerplate.
GitHub: https://github.com/RaptorVampire/tranq
Why tranq?
Writing repetitive try/except blocks clutters your code. tranq gives you declarative error handling with decorators, context managers, and smart retry strategies.
- 🧘 Tranquil – clean, readable, and maintainable.
- 🔁 Smart retries – exponential, linear, Fibonacci backoff, jitter, and max delay.
- 🚦 Circuit Breaker – prevent cascading failures (sync & async).
- 🧪 Conditional retry – on specific exceptions or result values.
- 📦 Retry groups – all-or-nothing execution for multiple functions.
- 📊 Built‑in metrics & profiling – monitor performance and error rates.
- 📝 Pluggable reporters – send errors to files, Sentry, Slack, or custom destinations.
- 🧩 Context manager API – use
with tranq.retry(...):when decorators aren't ideal. - 🔧 Stateful retry – persist attempt count across calls.
- 🎭 Mock error injection – test your error handling with ease.
Installation
pip install tranq
Requires Python 3.9 or later.
Quick Start
Decorator (@handle)
import tranq
@tranq.handle(on=ValueError, retry=3, delay=0.5, backoff=2.0)
def risky():
...
Async (@handle_async)
@tranq.handle_async(on=ConnectionError, retry=2, fallback=lambda: "offline")
async def fetch_data():
...
Circuit Breaker
cb = tranq.CircuitBreaker(failure_threshold=5, timeout=60)
@tranq.handle(circuit_breaker=cb)
def call_unstable_service():
...
Context Manager
with tranq.retry(on=ValueError, retry=2) as ctx:
result = ctx.run(my_function, arg1, arg2)
Retry Group
group = tranq.retry_group(step1, step2, step3, on=Exception, retry=1)
results = group.run()
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():
...
Conditional Retry
@tranq.handle(
on=requests.RequestException,
retry_if=lambda e: e.response.status_code == 429,
retry=3,
)
def call_api():
...
@tranq.handle(
retry_on_result=lambda result: result is None,
retry=2,
)
def get_data():
...
Error Handlers
@tranq.handle(
on=(ValueError, ConnectionError),
on_error={ValueError: log_warning, ConnectionError: alert_admin},
)
def process():
...
Circuit Breaker (Sync & Async)
from tranq import CircuitBreaker, AsyncCircuitBreaker
cb = CircuitBreaker(failure_threshold=3, timeout=30)
acb = AsyncCircuitBreaker(failure_threshold=3, timeout=30)
Reporters
from tranq import FileReporter, SentryReporter, SlackReporter
reporters = [
FileReporter("/var/log/tranq_errors.log"),
SentryReporter(dsn="..."),
SlackReporter(webhook_url="..."),
]
@tranq.handle(on=Exception, reporters=reporters)
def critical_task():
...
Metrics & Profiling
@tranq.handle(metrics=True, metric_prefix="myapp")
def expensive_op():
...
from tranq import get_metrics, profile, get_profile
@profile
def heavy_computation():
...
print(get_metrics())
print(get_profile("heavy_computation"))
Mock Error Injection
from tranq import mock_errors
with mock_errors(ValueError, probability=0.8):
result = my_function()
Dependency Injection
@tranq.handle(inject={"logger": logging.getLogger("app")})
def do_work(logger=None):
logger.info("Working...")
Advanced Examples
Combining Features
cb = CircuitBreaker(failure_threshold=3, timeout=60)
@tranq.handle(
on=requests.RequestException,
retry=5,
backoff_strategy="fibonacci",
max_delay=30,
jitter=True,
retry_if=lambda e: e.response.status_code in (429, 503),
circuit_breaker=cb,
metrics=True,
metric_prefix="api",
reporters=[FileReporter("api_errors.log")],
fallback=lambda: {"status": "fallback"},
)
def fetch_from_external_api():
...
Retry Group with Mixed Sync/Async
from tranq import retry_group, async_retry_group
group = retry_group(step1, step2, on=ValueError, retry=2)
results = group.run()
async_group = async_retry_group(step1, step3, on=Exception, retry=1)
results = await async_group.run()
API Reference
· Decorators: handle(...), handle_async(...) · Context Manager: retry(...) · Retry Groups: retry_group(...), async_retry_group(...) · Circuit Breakers: CircuitBreaker(...), AsyncCircuitBreaker(...) · Policies: Policy, set_global_policy(), get_global_policy() · Reporters: Reporter, FileReporter, SentryReporter, SlackReporter · Utilities: get_metrics(), reset_metrics(), profile(), get_profile(), mock_errors() · Exceptions: TranqError, RetryExhaustedError, CircuitBreakerError, ResultNotAcceptedError, RetryGroupError
License
MIT © RaptorVampire
Happy error handling! 🧘
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 tranq-0.2.7.tar.gz.
File metadata
- Download URL: tranq-0.2.7.tar.gz
- Upload date:
- Size: 15.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adf7a69778957209753369f45d8b252b4d8c3083ccbdb859c0d4bfe3fa9deb51
|
|
| MD5 |
280d77980ffcb462fbb300cf33ff3e4b
|
|
| BLAKE2b-256 |
80fa0ae6fd0f88037885f4ac84ccb75ed855708a2eae4370dfa652fe091248a6
|
File details
Details for the file tranq-0.2.7-py3-none-any.whl.
File metadata
- Download URL: tranq-0.2.7-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ebd9ff08e8376e7443cee8e21bbe1a9b94564fe9be444611a53abb25eac60ac
|
|
| MD5 |
7a06f2177b045bb26fd594a841695be4
|
|
| BLAKE2b-256 |
a2a07e60ae620ca7050bc6ed0cb76a9f0665b625b99879917d11df8e19cb1d69
|