Skip to main content

Stario

Stario
Craft realtime hypermedia apps that are a joy to write and ship.

Documentation · Source


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 you start the built-in HTTP server (TCP or a Unix domain socket) with asyncio.run(stario.serve(bootstrap)) (or uvloop.run(...)) or the stario CLI. 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.12 or newer is required.

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

To start the same app from Python, await serve on a loop you start (and continue after shutdown):

import asyncio
from stario import serve

if __name__ == "__main__":
    asyncio.run(serve(bootstrap))

Use uvloop.run(serve(bootstrap)) when you want uvloop. Pass listen settings as keywords: serve(bootstrap, host="0.0.0.0", port=9000). Server takes a ServerConfig object.

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 single yield, Route endpoints, no hidden registration.
  • Sharp primitives: Context for the request, Writer for the response, HTML/SVG trees via stario.markup, telemetry via span.
  • Files: Assets and Files expose a directory at a URL prefix. attach(app) registers GET/HEAD and loads the tree. Assets hashes names and 307s the logical path. Both use strong ETags and 304. Import from stario or stario.filesystem. stario.staticassets is 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

The bump commit is the source of truth. The tag must name that version. There is no autotag, and the build does not rewrite the version.

  1. Keep notes under ## Unreleased in CHANGELOG.md.
  2. When those notes are the release, one commit:
    • set version in pyproject.toml (for example 4.3.0)
    • move ## Unreleased to ## 4.3.0 - YYYY-MM-DD and leave an empty ## Unreleased above it
  3. Tag that commit and push:
git tag v4.3.0
git push origin v4.3.0

A GitHub Release with the same tag is the same event. The workflow tests 3.12–3.14, checks that the tag, pyproject.toml, and changelog agree, then uploads stario-4.3.0 to PyPI. If the tag does not match the committed version, the job fails.

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.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stario 4.3.0
File Size Uploaded
stario-4.3.0.tar.gz 145.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stario 4.3.0
File Interpreter ABI Platform
stario-4.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 325.1 kB

Release files / stario-4.3.0.tar.gz

Download URL stario-4.3.0.tar.gz
Size 145.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0ef6f054c572d6e14e93a4bf8d4a729cb831e8f5177c6464753e2d6a0f25a090
BLAKE2b-256 checksum
How to use checksums
602e5ec4c474c3b2cc1488f904318a249de265040f28850efc320ce2f02dbe0e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / stario-4.3.0-py3-none-any.whl

Download URL stario-4.3.0-py3-none-any.whl
Size 179.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8518414990e0b665f324ff0371be9b738c83dd8deb2dc08985fe5b5b5c6e22b5
BLAKE2b-256 checksum
How to use checksums
b637f7ffbc5064b0726c676e1330536cd981ae813862f9a24d4c9f9c5e5611c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

4.3.0 This release

2 release files

4.2.0

2 release files

4.1.1

2 release files

4.1.0

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.4.0

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

3 release files

2.3.0

2 release files

2.2.1

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.5

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page