Metadata-Version: 2.4
Name: tun
Version: 0.0.1
Summary: A lightweight ASGI web framework for Python.
License-Expression: MIT
License-File: LICENSE
Requires-Dist: uvicorn>=0.35.0 ; extra == 'server'
Requires-Python: >=3.14
Provides-Extra: server
Description-Content-Type: text/markdown

# Tun

A lightweight ASGI web framework for Python.

> [!NOTE]
> Tun is in early development and is not yet ready for production use.

Requires Python 3.14 or newer.

## Run the example

From this repository:

```sh
uv run --extra server examples/hello.py
```

Open <http://127.0.0.1:8000/> to see `Hello`.

Alternatively, run the same mux directly with Uvicorn:

```sh
uv run --extra server uvicorn examples.hello:mux --host 127.0.0.1 --port 8000
```

## API

```python
from tun import Request, ResponseWriter, ServeMux, listen_and_serve

mux = ServeMux()


async def hello(w: ResponseWriter, r: Request) -> None:
    w.set_header("content-type", "text/plain")
    await w.write("Hello")


mux.handle_func("/", hello, method="GET")

if __name__ == "__main__":
    listen_and_serve("127.0.0.1:8000", mux)
```

- Routes match exact paths and methods; `GET` is the default registration method.
  There are no path parameters, automatic `HEAD` routes, or automatic `OPTIONS` responses.
- Requests expose `method`, `path`, raw `query_string` bytes, and lowercase
  `headers`. Repeated header names retain the last value; original headers remain
  available in `r.scope["headers"]`. Use `await r.body()` to read the entire body
  into memory; there is currently no body-size limit.
- `set_header()` replaces a response header. `await w.write_header(201)` sends
  an explicit status; otherwise the first write uses 200. Headers cannot change
  once sent. Strings are encoded as UTF-8; content types must be set explicitly.
- Each `write()` sends a response chunk. Tun closes the response when the handler
  returns, including handlers that write nothing (an empty 200 response).
- Missing paths return 404; unmatched methods return 405 with an `Allow` header.
- Handler exceptions propagate to the ASGI server. Once a response has started,
  an error cannot replace it with a 500 response.

The core has no runtime dependencies. The optional `server` extra provides Uvicorn
for the blocking `listen_and_serve()` helper. Alternatively, use any HTTP ASGI
server with the mux directly. WebSockets are not supported yet.
