Metadata-Version: 2.4
Name: tranq
Version: 0.2.3
Summary: Modern, decorator-based error handling for Python – calm your code.
Author-email: RaptorVampire <mhman884@gmail.com>
License: MIT
Keywords: error-handling,retry,decorator,logging,tranquil,async,circuit-breaker
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: rich>=13.0
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: black>=23; extra == "dev"
Dynamic: license-file

# tranq

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

[![GitHub](https://img.shields.io/badge/GitHub-RaptorVampire%2Ftranq-blue?logo=github)](https://github.com/RaptorVampire/tranq)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**GitHub:** [https://github.com/RaptorVampire/tranq](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

```bash
pip install tranq
```

Requires Python 3.9 or later.

---

Quick Start

Decorator (@handle)

```python
import tranq

@tranq.handle(on=ValueError, retry=3, delay=0.5, backoff=2.0)
def risky():
    ...
```

Async (@handle_async)

```python
@tranq.handle_async(on=ConnectionError, retry=2, fallback=lambda: "offline")
async def fetch_data():
    ...
```

Circuit Breaker

```python
cb = tranq.CircuitBreaker(failure_threshold=5, timeout=60)
@tranq.handle(circuit_breaker=cb)
def call_unstable_service():
    ...
```

Context Manager

```python
with tranq.retry(on=ValueError, retry=2) as ctx:
    result = ctx.run(my_function, arg1, arg2)
```

Retry Group

```python
group = tranq.retry_group(step1, step2, step3, on=Exception, retry=1)
results = group.run()
```

---

Features in Depth

Retry with Backoff

```python
@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

```python
@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

```python
@tranq.handle(
    on=(ValueError, ConnectionError),
    on_error={ValueError: log_warning, ConnectionError: alert_admin},
)
def process():
    ...
```

Circuit Breaker (Sync & Async)

```python
from tranq import CircuitBreaker, AsyncCircuitBreaker

cb = CircuitBreaker(failure_threshold=3, timeout=30)
acb = AsyncCircuitBreaker(failure_threshold=3, timeout=30)
```

Reporters

```python
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

```python
@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

```python
from tranq import mock_errors

with mock_errors(ValueError, probability=0.8):
    result = my_function()
```

Dependency Injection

```python
@tranq.handle(inject={"logger": logging.getLogger("app")})
def do_work(logger=None):
    logger.info("Working...")
```

---

Advanced Examples

Combining Features

```python
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

```python
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! 🧘
