Start · one route, no ceremony
Your first Wreath application.
Create a typed ASGI application, run it on Wreath's development server, and keep the boundary small enough to understand completely.
Install#
Wreath targets CPython 3.14. With an existing uv project:
uv add wreathOr create a clean project first:
uv init --python 3.14
uv add wreathCreate the application#
from dataclasses import dataclass
from typing import Annotated
from wreath import Request, Wreath
from wreath.binding import Body
app = Wreath()
@dataclass
class Greeting:
name: str
@app.get("/hello/{name}")
async def hello(request: Request, name: str) -> dict:
return {"hello": name}
@app.post("/hello")
async def create_greeting(
request: Request,
greeting: Annotated[Greeting, Body()],
) -> dict:
return {"hello": greeting.name}Path parameters and the dataclass body are bound from the handler signature. An invalid request is rejected before the handler runs.
Run it#
uv run wreath dev app:appThen call both routes:
curl http://127.0.0.1:8000/hello/Mara
curl -X POST http://127.0.0.1:8000/hello \
-H 'content-type: application/json' \
-d '{"name":"Mara"}'The application is ordinary ASGI. If another conforming server is already part of your deployment, keep it:
uv run uvicorn app:appTest the boundary#
Wreath's test client drives the ASGI application without opening a socket.
from wreath.testing import TestClient
from app import app
async def test_hello() -> None:
async with TestClient(app) as client:
response = await client.get("/hello/Mara")
assert response.status_code == 200
assert response.json() == {"hello": "Mara"}Run it through the project test runner:
uv run wreath testFor a complete application rather than another isolated snippet, explore the canonical camera-trap service. It combines models, migrations, typed queries, authorization, deterministic seed data and a runnable PostgreSQL-backed API.
Add the difficult part next#
Do not add architecture because a framework tour says it exists. Choose the pressure your product actually has: