Metadata-Version: 2.4
Name: httpx-debug
Version: 0.2.0
Summary: Failure-aware curl output for httpx: reproduce any (failing) request as a paste-able, secret-redacted curl command — printed or copied to your clipboard, even over SSH.
Project-URL: Homepage, https://github.com/mayurrawte/httpx-debug
Project-URL: Repository, https://github.com/mayurrawte/httpx-debug
Project-URL: Issues, https://github.com/mayurrawte/httpx-debug/issues
Project-URL: Changelog, https://github.com/mayurrawte/httpx-debug/blob/main/CHANGELOG.md
Author: Mayur Rawte
License-Expression: MIT
License-File: LICENSE
Keywords: clipboard,curl,debug,debugging,devtools,httpx
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Debuggers
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.23
Provides-Extra: requests
Requires-Dist: requests>=2.25; extra == 'requests'
Description-Content-Type: text/markdown

# httpx-debug

[![CI](https://github.com/mayurrawte/httpx-debug/actions/workflows/ci.yml/badge.svg)](https://github.com/mayurrawte/httpx-debug/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/httpx-debug.svg)](https://pypi.org/project/httpx-debug/)
[![Python versions](https://img.shields.io/pypi/pyversions/httpx-debug.svg)](https://pypi.org/project/httpx-debug/)
[![License: MIT](https://img.shields.io/pypi/l/httpx-debug.svg)](https://github.com/mayurrawte/httpx-debug/blob/main/LICENSE)

**Turn any [httpx](https://www.python-httpx.org/) request — especially a failing one — into a ready-to-paste, secret-redacted `curl` command.**

Attach `httpx-debug` to a client once. Whenever a request fails — a `4xx`/`5xx` response, a timeout, or a connection error — it prints the exact `curl` that reproduces the call, with your credentials redacted:

```
# httpx-debug: 401 Unauthorized ← POST https://api.example.com/v1/users
curl -X POST -H 'Authorization: <redacted>' -H 'Content-Type: application/json' -d '{"name": "mayur"}' https://api.example.com/v1/users
```

No more sprinkling `print()` around your HTTP code or hand-assembling a curl command to reproduce a bug.

## Quickstart

```bash
pip install httpx-debug     # zero dependencies beyond httpx
```

```python
import httpx, httpx_debug

client = httpx.Client()
httpx_debug.attach(client)              # failing requests now print a paste-able curl

client.post("https://api.example.com/v1/users", json={"name": "mayur"})
```

Works the same with `httpx.AsyncClient`, and with any library that accepts an httpx client — including the **OpenAI and Anthropic SDKs** (`http_client=`), so you can see exactly what your LLM calls send over the wire.

---

## Why not curlify?

[curlify](https://pypi.org/project/curlify/) and friends convert a request object to curl — and that's it. `httpx-debug` is the debugging layer around that:

- **Failure-aware** — attach once, hear about it only when something breaks. No call-site changes.
- **Secret redaction by default** — sensitive headers (`Authorization`, `Cookie`, `X-API-Key`, …), URL userinfo passwords (`https://user:••••@host`), and sensitive query-string values (`?api_key=…`, `?token=…`) become `<redacted>` so you can paste the command into an issue or Slack without leaking credentials.
- **Clipboard, even over SSH** — uses `pbcopy`/`xclip`/`wl-copy`/`clip` locally and falls back to the OSC 52 terminal escape, which copies to your *local* clipboard from a remote shell.
- **Async-first** — works identically with `httpx.Client` and `httpx.AsyncClient`.
- **Sees exceptions too** — the drop-in `httpx_debug.Client` / `AsyncClient` also capture timeouts and connection errors, not just error status codes.

## Usage

### Convert a request to curl

```python
import httpx_debug

curl = httpx_debug.to_curl(request)                 # secrets redacted
curl = httpx_debug.to_curl(request, redact=False)   # raw
```

### Copy to clipboard

```python
httpx_debug.copy(request)   # returns True if a clipboard was reachable
```

### Attach to any client (yours or an SDK's)

```python
client = httpx.AsyncClient()
httpx_debug.attach(client)              # print curl for 4xx/5xx responses
httpx_debug.attach(client, on="all")    # ...or for every request
httpx_debug.attach(client, copy=True)   # also copy to clipboard
httpx_debug.detach(client)              # remove
```

`attach` uses httpx event hooks, so it can't observe transport exceptions (timeouts, DNS failures). For those, use the drop-in clients:

### Drop-in clients (also capture timeouts & connection errors)

```python
client = httpx_debug.AsyncClient(base_url="https://api.example.com")
# behaves exactly like httpx.AsyncClient, plus curl output on any failure
```

### Using it with `requests`

`requests` is supported too — install the extra:

```bash
pip install 'httpx-debug[requests]'
```

The same functions auto-detect and accept `requests` objects, plus there's a drop-in `Session`:

```python
import requests, httpx_debug

session = requests.Session()
httpx_debug.attach(session)                    # curl for 4xx/5xx responses
httpx_debug.to_curl(prepared_request)          # requests.PreparedRequest → curl

# drop-in Session ALSO captures timeouts / connection errors
session = httpx_debug.Session()                # then use it like a normal Session
```

All options (`on`, `copy`, `redact`, redaction rules, …) work identically; the drop-in `Session` uses the same `debug_*`-prefixed kwargs as the httpx drop-ins.

### Options

| Option | Default | |
|---|---|---|
| `on` | `"error"` | `"error"` (status ≥ 400 + exceptions) or `"all"` |
| `copy` | `False` | also copy the curl command to the clipboard |
| `redact` | `True` | replace sensitive header values with `<redacted>` |
| `copy_redact` | follows `redact` | redaction for the *clipboard* copy specifically |
| `redact_headers` | built-in set | which headers count as sensitive (lowercase) |
| `file` | `sys.stderr` | where to print |

On the drop-in clients the same options are prefixed: `debug_on`, `debug_copy`, `debug_redact`, `debug_copy_redact`, `debug_redact_headers`, `debug_file`.

### Print safe, copy replayable

The printed command ends up in logs and screenshots — keep it redacted. The clipboard's job is *replaying* the request, which needs real credentials:

```python
httpx_debug.attach(client, copy=True, copy_redact=False)
# terminal: Authorization: <redacted>     ← safe to paste in an issue
# clipboard: Authorization: Bearer sk-…   ← actually runs
```

Raw copying is always opt-in — remember clipboard managers keep history.

## Notes

- **Request bodies are not redacted.** A JSON/form body containing a `password`, `client_secret`, etc. is shown verbatim — redaction covers headers, URL userinfo, and query strings only. Review before sharing, or drop the body.
- Streamed request bodies are never consumed by httpx-debug; the curl output notes `# request body was streamed and is not shown`.
- Non-UTF-8 bodies are omitted with a byte-count comment.
- Debug tooling must never break your app: clipboard and hook failures are swallowed.

## Roadmap

- pytest plugin: show the curl of the last failed request under the traceback
- response capture alongside the request
- a Node.js sibling for server-side `fetch`

## License

MIT
