Metadata-Version: 2.4
Name: backoffkit
Version: 0.1.0
Summary: Retry decorator with exponential backoff and jitter. Works with sync and async functions. Zero dependencies.
Project-URL: Homepage, https://github.com/CoderSufiyan/backoffkit
Project-URL: Repository, https://github.com/CoderSufiyan/backoffkit
Project-URL: Issues, https://github.com/CoderSufiyan/backoffkit/issues
License: MIT
License-File: LICENSE
Keywords: backoff,exponential,fault-tolerance,jitter,resilience,retry
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# backoffkit

> Retry decorator with exponential backoff and jitter. Works with sync and async functions. Zero dependencies.

[![PyPI version](https://img.shields.io/pypi/v/backoffkit.svg)](https://pypi.org/project/backoffkit/)
[![Python versions](https://img.shields.io/pypi/pyversions/backoffkit.svg)](https://pypi.org/project/backoffkit/)
[![CI](https://github.com/CoderSufiyan/backoffkit/actions/workflows/ci.yml/badge.svg)](https://github.com/CoderSufiyan/backoffkit/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/CoderSufiyan/backoffkit/blob/main/LICENSE)
[![Open Source](https://img.shields.io/badge/Open%20Source-%E2%9D%A4-red)](https://github.com/CoderSufiyan/backoffkit)

---

Any time your code calls something external — an API, a database, a third-party service — it can fail temporarily. The fix is to retry, but retrying naively (immediately, forever) makes things worse.

**backoffkit** gives you smart retry logic in one decorator:

```python
from backoffkit import retry

@retry(times=3, backoff="exponential")
def call_api():
    response = requests.get("https://api.example.com/data")
    response.raise_for_status()
    return response.json()
```

It works the same way for async functions:

```python
@retry(times=3, backoff="exponential")
async def fetch_data():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/data")
        response.raise_for_status()
        return response.json()
```

---

## Install

```bash
pip install backoffkit
```

---

## Usage

### Basic retry

```python
from backoffkit import retry

@retry(times=3)
def call_api():
    ...
```

### Retry only on specific exceptions

```python
@retry(times=3, on=[ConnectionError, TimeoutError])
def call_api():
    ...
```

### Log retries with a callback

```python
import logging

@retry(
    times=5,
    on=[ConnectionError],
    on_retry=lambda attempt, exc: logging.warning(f"Retry {attempt}: {exc}")
)
def call_api():
    ...
```

### Async support

```python
@retry(times=3, backoff="exponential")
async def fetch():
    ...
```

---

## Backoff strategies

| Strategy      | Description                          | Delays (delay=1s)     |
|---------------|--------------------------------------|-----------------------|
| `exponential` | Doubles each attempt (default)       | 1s, 2s, 4s, 8s...    |
| `linear`      | Increases by delay each attempt      | 1s, 2s, 3s, 4s...    |
| `fixed`       | Same delay every attempt             | 1s, 1s, 1s, 1s...    |

### Jitter

Jitter adds a small random noise to each delay to prevent the [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem) — when many clients retry at exactly the same time and overwhelm the server. Enabled by default.

```python
@retry(times=3, backoff="exponential", jitter=True)  # default
def call_api():
    ...
```

---

## All options

```python
@retry(
    times=3,           # Total attempts including the first (default: 3)
    delay=1.0,         # Initial delay in seconds (default: 1.0)
    backoff="exponential",  # "fixed", "linear", or "exponential" (default: "exponential")
    jitter=True,       # Add random noise to delay (default: True)
    max_delay=60.0,    # Cap on delay in seconds (default: 60.0)
    on=[Exception],    # Exceptions to retry on (default: all)
    on_retry=None,     # Callback: fn(attempt: int, exc: Exception) -> None
)
```

---

## Error handling

When all attempts are exhausted, `RetryError` is raised with the attempt count and the last exception:

```python
from backoffkit import retry, RetryError

@retry(times=3, delay=0)
def fn():
    raise ConnectionError("service down")

try:
    fn()
except RetryError as e:
    print(e.attempts)         # 3
    print(e.last_exception)   # ConnectionError: service down
```

---

## Production use

backoffkit is designed for production backend systems. Use it when calling:

- External REST APIs
- Databases that may have transient connection failures
- Message queues
- Any I/O-bound operation that can fail temporarily

The exponential backoff + jitter combination is the [industry-standard approach](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/) used by AWS, Google, and Stripe SDKs.

---

## Why not `tenacity`?

`tenacity` is powerful but has a large API surface. `backoffkit` covers the 95% use case with a single decorator and zero dependencies — no learning curve, no extras to install.

---

## Open Source

backoffkit is MIT licensed and open for contributions. See [CONTRIBUTING.md](CONTRIBUTING.md).

Things we'd love help with:
- Async callback support in `on_retry`
- `on_success` and `on_failure` hooks
- Retry budget (max total wait time across all retries)
- More backoff strategies (fibonacci, decorrelated jitter)

---

## License

[MIT](LICENSE) © Sufiyan Khan
