Metadata-Version: 2.5
Name: cancelreq
Version: 0.1.0
Author-email: Varun Ramesh <varun@goguava.ai>
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# cancelreq

A blocking HTTP/1.1 client whose in-flight requests can be cancelled. Plain HTTP only, no TLS.

## Quickstart

```python
import threading

from cancelreq import get

cancel = threading.Event()
body = get(cancel, "http://example.com/")
```

Set the event from any thread to abort a request that is already running:

```python
import threading

from cancelreq import RequestCancelled, get

cancel = threading.Event()
threading.Timer(0.5, cancel.set).start()

try:
    body = get(cancel, "http://example.com/slow")
except RequestCancelled:
    print("gave up")
```

POST a body, with headers and a timeout:

```python
from cancelreq import post

body = post(
    cancel,
    "http://example.com/api",
    body='{"answer": 42}',
    headers={"Content-Type": "application/json"},
    timeout=5.0,
)
```

## API

```
request(cancel, method, url, *, body=None, headers=None, timeout=None) -> bytes
get(cancel, url, *, headers=None, timeout=None) -> bytes
post(cancel, url, *, body=None, headers=None, timeout=None) -> bytes
```

`get` and `post` are shortcuts for `request` with that method.

Returns the response body. Raises `RequestCancelled`, `HTTPStatusError` (4xx/5xx, carrying
`.status`, `.reason` and `.body`), `TimeoutError`, `ConnectionError`, or `ProtocolError`.

`timeout` defaults to `None`, meaning the request ends only when the server or `cancel` ends it.

## Cancellation

Best effort, and honoured within ~50ms while connecting, while writing the request, and while
waiting for the response. A host lookup already in flight runs to completion first.

## Development

```sh
make test    # pytest
make check   # ty and ruff
```
