Skip to main content

Modern, decorator-based error handling for Python – calm your code.

Project description

tranq

Calm error handling for Python – decorator-based, zero boilerplate.

GitHub License: MIT

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

tranq-0.2.4.tar.gz (15.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tranq-0.2.4-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

File details

Details for the file tranq-0.2.4.tar.gz.

File metadata

  • Download URL: tranq-0.2.4.tar.gz
  • Upload date:
  • Size: 15.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for tranq-0.2.4.tar.gz
Algorithm Hash digest
SHA256 92fc5b01a92724ffba2bd5f7142770ca288c829d3880ff29582e13208451040d
MD5 84b147597dddebf5c0eb0488f7eb90c3
BLAKE2b-256 f0a8f30d4628e6e74d5f2a0769f1c5ed6e95902e4fcce734c8e3a10ca84bbd0f

See more details on using hashes here.

File details

Details for the file tranq-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: tranq-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 14.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for tranq-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 46d3e76a51126f5c7d175bce474c4d71f5393669dc0c94d811505143241d61ca
MD5 b7358c4fa59898b70244661d57444f94
BLAKE2b-256 2b1629362c7ec1d2a2778d5ec94160b99fb66610283392c37e321f5d836fec94

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page