Stario
Craft realtime hypermedia apps that are a joy to write and ship.
Stario is a small Python framework for enjoyable realtime hypermedia apps. It helps you build web apps where HTTP, HTML, and streaming stay visible in your code. Handlers are plain async functions; routes are registered explicitly; responses go through a dedicated writer. When the UI needs live updates, you can add Datastar and Relay without throwing away the same request/response mental model. The idea is Go-to architecture. The SDK and tiles tutorial live here.
Full guides, API reference, and tutorials live at stario.dev. This page is a short orientation for people landing on the repository.
Where Stario fits
Stario is an asyncio-native HTTP stack: you write async handlers and register routes on an App, and the stario CLI runs a built-in HTTP server (TCP or a Unix domain socket). It is not an ASGI application you mount in Uvicorn or Hypercorn; wiring goes through the bootstrap hook, Context, and Writer instead.
Requirements
Python 3.14 or newer is required. The package tracks current Python and the standard library (including APIs the framework builds on) rather than supporting older runtimes.
uvloop (optional): Stario defaults to the stdlib asyncio loop. For a faster event loop on Linux/macOS, install the optional extra and set STARIO_LOOP=uvloop:
uv add "stario[uvloop]"
# or: pip install "stario[uvloop]"
Then run with STARIO_LOOP=uvloop stario serve main:bootstrap (or stario watch). uvloop is not supported on Windows.
JSON codec
Stario uses one process-wide JSON codec for responses, Datastar signals, telemetry, and the test client. The default codec uses the standard library and emits compact UTF-8 JSON. Replace it explicitly when the application uses another library:
import msgspec
import stario.json as stario_json
class MsgspecCodec:
def dumps(self, value, *, default=None):
return self.dumps_bytes(value, default=default).decode()
def dumps_bytes(self, value, *, default=None):
return msgspec.json.encode(value, enc_hook=default)
def loads(self, data):
return msgspec.json.decode(data)
stario_json.set_codec(MsgspecCodec())
orjson has native byte output, so its byte path does not encode text first:
import orjson
import stario.json as stario_json
class OrjsonCodec:
def dumps(self, value, *, default=None):
return self.dumps_bytes(value, default=default).decode()
def dumps_bytes(self, value, *, default=None):
return orjson.dumps(value, default=default)
def loads(self, data):
return orjson.loads(data)
stario_json.set_codec(OrjsonCodec())
dumps() returns text, dumps_bytes() returns UTF-8 bytes, and loads()
accepts text, bytes, or a byte array. Stario uses bytes for HTTP and SSE and
text for HTML attributes and telemetry storage. A byte-native codec only
decodes when a text consumer asks for dumps().
Calling set_codec() again replaces the codec for later operations. Stario
does not synchronize replacement with active requests or telemetry writes.
Configure during application setup unless changing live serialization is
intentional. The default callback is backend-dependent: a codec may serialize
its native datetime, UUID, Decimal, or model types before calling it.
This is transport configuration only; validation and application models stay in application code.
Quick start
From an example
Clone the repo (or copy an example directory) and run:
git clone https://github.com/bobowski/stario.git
cd stario/examples/tiles
uv sync
uv run stario watch main:bootstrap
See examples/ for tiles (recommended), hello-world, and chat-room (multi-file layout).
Manual setup
uv init my-app # creates a new uv project (pyproject, layout)
cd my-app
uv add stario
Put this in main.py:
import stario.responses as responses
from stario import App, Context, Route, Span, Writer
async def home(c: Context, w: Writer) -> None:
responses.text(w, "Hello from Stario")
HOME = Route("GET", "/")
async def bootstrap(app: App, span: Span):
span.attr("app.name", "example")
app.add(HOME, home)
yield
uv run stario watch main:bootstrap
Install with pip install stario if you are not using uv. During startup, bootstrap runs until its single yield: register routes and attach attributes to span before yield; put teardown after yield when needed. Use stario watch in development so the process reloads when files change; use stario serve for a normal long-running server without reload. Server runtime policy (STARIO_HOST, STARIO_PORT, STARIO_TRACER, and related vars) is configured through environment variables — see stario serve --help (Stario does not load .env files; export vars in your shell or use your own dotenv tooling). See Getting started for project layout. For containers, TLS, and production-oriented setup, see Deployment, containers, and TLS.
Filesystem URLs
Build Assets or Files at module level. Call href() there. Call
await attach(app) in bootstrap (register + load). Assets precompresses
by default; Files does not unless you pass precompress=:
from stario import App, Assets, Files, Span
ASSETS = Assets("./static", "/static")
UPLOADS = Files("./uploads", "/data")
STYLE_CSS = ASSETS.href("css/style.css")
async def bootstrap(app: App, span: Span):
span.attrs(await ASSETS.attach(app))
await UPLOADS.attach(app, precompress=("br", "gzip"))
yield
Assets hashes names and 307s the logical path. Both send strong ETags
and X-Content-Type-Options: nosniff. stario.staticassets is obsolete.
What you get
- Explicit wiring: async-generator
bootstrap(app, span)with a singleyield,Routeendpoints, no hidden registration. - Sharp primitives:
Contextfor the request,Writerfor the response, HTML/SVG trees viastario.markup, telemetry viaspan. - Files:
AssetsandFilesexpose a directory at a URL prefix.attach(app)registers GET/HEAD and loads the tree.Assetshashes names and 307s the logical path. Both use strong ETags and 304. Import fromstarioorstario.filesystem.stario.staticassetsis obsolete. - Hypermedia by default: HTML and SSE are first-class; realtime layers are optional when the product needs them.
- Observable runs: spans for startup and requests are part of how you structure apps, not an afterthought.
What Stario is not
No bundled ORM, admin UI, or plugin discovery system. Databases, auth, and brokers stay in your code or thin adapters; the framework stays a focused HTTP and hypermedia core.
Releases
Version history and upgrade notes live in CHANGELOG.md.
Contributing
From stario/:
uv sync
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest
Before committing:
uv run ruff check . --fix
uv run ruff format .
Release files for stario 4.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stario-4.2.0.tar.gz | 144.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stario-4.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 322.0 kB
Release files / stario-4.2.0.tar.gz
| Download URL | stario-4.2.0.tar.gz |
|---|---|
| Size | 144.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df6b80b65812c0c774ce2f6487b95c9887c48442203ee4471d42d8140bfd3826
|
|
BLAKE2b-256 checksum How to use checksums |
a365d736062134b2f5ee48a53764b3d9b77a65694e51e4ac10402767719433b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / stario-4.2.0-py3-none-any.whl
| Download URL | stario-4.2.0-py3-none-any.whl |
|---|---|
| Size | 177.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e7bb0f9959394fac9a64e5325366f76ec9fcd0b035c618d8a92ca7e9f5d97c9e
|
|
BLAKE2b-256 checksum How to use checksums |
b06ca81c1193999571f980ef71be9d536bb7ebc50d4118e84a89ecb42e9d530c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|