Metadata-Version: 2.4
Name: dark-req
Version: 0.1.0
Summary: A requests-compatible HTTP client that fixes requests' flaws: sane default timeouts, auto-retry with backoff, rate limiting, hooks, base_url sessions, downloads with progress, and async support.
Author: dark-req contributors
License: MIT
Keywords: http,requests,client,api,retry
Classifier: Development Status :: 4 - Beta
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
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: urllib3>=2.0
Requires-Dist: certifi
Dynamic: license-file

# dark-req

A **requests-compatible** Python HTTP client that fixes everything `requests`
gets wrong — in the same lightweight footprint (only `urllib3` + `certifi`).

```python
import dark_req

# Works exactly like requests...
r = dark_req.get("https://api.example.com/users", timeout=10)
print(r.status_code, r.json())

# ...but with superpowers baked in
s = dark_req.Session(
    base_url="https://api.example.com",
    max_retries=3,          # auto-retry on 429/5xx + connection errors
    rate_limit=5,           # max 5 req/sec per host (token bucket)
    hooks={"on_error": [lambda e: print("failed:", e)]},
)
r = s.get("/users", params={"page": 2})   # -> https://api.example.com/users?page=2
r.raise_for_status()

# Downloads with progress, one line
s.download("https://example.com/big.zip", "big.zip",
           progress=lambda done, total: print(f"{done}/{total}"))

# Async, same API
import asyncio
async def main():
    async with dark_req.AsyncSession(base_url="https://api.example.com") as s:
        users, repos = await asyncio.gather(s.get("/users"), s.get("/repos"))
asyncio.run(main())
```

## Install

```bash
pip install dark-req
```

Requires Python 3.9+.

## Why not just `requests`?

| Pain point | `requests` | `dark-req` |
|---|---|---|
| Default timeout | ❌ none — hangs forever | ✅ connect=5s, read=30s |
| Retries | ❌ manual | ✅ auto-retry on 429/5xx + network errors, exponential backoff + jitter, honors `Retry-After` |
| Rate limiting | ❌ | ✅ per-host token bucket (`rate_limit=5`) |
| `base_url` on sessions | ❌ | ✅ |
| Hooks | ❌ (only transport adapters) | ✅ `on_request` / `on_response` / `on_error` |
| Download with progress | ❌ manual streaming loop | ✅ `session.download(url, path, progress=...)` |
| Async | ❌ sync only | ✅ `AsyncSession`, same API |
| Bearer auth helper | ❌ | ✅ `BearerAuth("token")` / `(user, pass)` tuples |
| `raise_for_status` message | ❌ bare status | ✅ includes URL + body snippet |
| `response.json()` errors | ❌ cryptic `JSONDecodeError` | ✅ `dark_req.JSONDecodeError` (also a `ValueError`), carries request/response |
| Retry exhaustion | ❌ silent last response | ✅ `RetryExhausted` with `attempts` + `last_exception` |
| `codes` | ✅ | ✅ `dark_req.codes.ok == 200` |

Everything else stays familiar: `get/post/put/patch/delete/head/options`,
`Session`, `Request`/`PreparedRequest`/`Response`, `HTTPBasicAuth`,
and an exception hierarchy mirroring `requests` (so existing `except`
blocks keep working).

## Sane defaults

```python
dark_req.get("https://slow.example.com")   # gives up after 5s connect / 30s read
dark_req.get("https://flaky.example.com")  # 3 automatic retries with backoff
```

Override per call with `timeout=...` (seconds, `(connect, read)` tuple, or
`timeout=None` to wait indefinitely), `max_retries=0` to disable retries,
`verify=False` to skip TLS verification.

## Roadmap

- `files=` multipart uploads
- Proxy support from environment variables
- HTTP/2 transport option
- Connection-level metrics / tracing hooks

## License

MIT
