Metadata-Version: 2.4
Name: resilient-httpx
Version: 0.1.0
Summary: Async HTTP client with retry, backoff, and proxy rotation
Project-URL: Homepage, https://github.com/galthran-wq/resilient-httpx
Project-URL: Repository, https://github.com/galthran-wq/resilient-httpx
Project-URL: Issues, https://github.com/galthran-wq/resilient-httpx/issues
Author: galthran-wq
License-Expression: MIT
License-File: LICENSE
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.0
Requires-Dist: structlog>=25.0.0
Requires-Dist: tenacity>=9.1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: respx; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# resilient-httpx

![python](https://img.shields.io/badge/python-3.10%2B-blue)
![coverage](https://img.shields.io/badge/coverage-99%25-brightgreen)

Async HTTP client with retry, backoff, and proxy rotation. Built on top of `httpx`, `tenacity`, and `structlog`.

## Installation

```bash
pip install git+https://github.com/galthran-wq/resilient-httpx.git
```

## Usage

### Basic (no proxies)

```python
from resilient_httpx import ProxyHttpClient, RetryPolicy

async with ProxyHttpClient(retry=RetryPolicy(max_attempts=5), timeout=15.0) as client:
    response = await client.get("https://api.example.com/data")
```

### With proxy rotation

```python
from resilient_httpx import ProxyHttpClient, RetryPolicy

client = ProxyHttpClient(
    proxies=[
        "http://proxy1:8080",
        "http://user:pass@proxy2:8080",
        "socks5://proxy3:1080",
    ],
    proxy_strategy="round-robin",  # or "random"
    retry=RetryPolicy(max_attempts=5, backoff="exponential"),
    timeout=15.0,
    blacklist_threshold=3,
    blacklist_ttl=300.0,
)

async with client:
    response = await client.get("https://api.example.com/data")
    data = response.json()
```

### Custom retry policy

```python
import httpx
from resilient_httpx import RetryPolicy

policy = RetryPolicy(
    max_attempts=5,
    backoff="exponential",       # "exponential" | "fixed" | "random_jitter"
    min_wait=1.0,
    max_wait=30.0,
    retry_on=[429, 500, 502, 503, 504],
    retry_on_exception=[httpx.TimeoutException, httpx.ConnectError],
)
```

### Error handling

```python
from resilient_httpx import ProxyHttpClient, AllProxiesExhausted, MaxRetriesExceeded

async with ProxyHttpClient(proxies=proxy_list) as client:
    try:
        response = await client.get("https://api.example.com/data")
    except AllProxiesExhausted:
        ...  # all proxies blacklisted
    except MaxRetriesExceeded as exc:
        ...  # retries exhausted, exc.__cause__ has the last error
```

## Configuration Reference

| Parameter | Type | Default | Description |
|---|---|---|---|
| `proxies` | `list[str] \| None` | `None` | Proxy URLs |
| `proxy_strategy` | `str` | `"round-robin"` | `"round-robin"` or `"random"` |
| `retry` | `RetryPolicy` | `RetryPolicy()` | Retry configuration |
| `timeout` | `float` | `30.0` | Request timeout in seconds |
| `headers` | `dict[str, str] \| None` | `None` | Default headers |
| `blacklist_threshold` | `int` | `3` | Consecutive failures before blacklisting |
| `blacklist_ttl` | `float` | `300.0` | Blacklist duration in seconds |
| `fallback_to_direct` | `bool` | `False` | Request without proxy when all are blacklisted |

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
```

Run with coverage:

```bash
pytest --cov=resilient_httpx --cov-report=term-missing
```
