Site your state, derive the wire — a Python→JS component compiler with an explicit client/server seam, for Litestar.
Project description
Site your state, derive the wire.
situ lets a Python developer write a reactive web UI as one component — real HTML on top, Python signals and handlers below — and declare, per piece of state, where it lives. A real Python→JS compiler reads that and emits a small, app-specific client island; the client/server boundary stays explicit and is enforced at compile time. You author no client JavaScript, and you can read every line the compiler ships.
The compiler and the mount core are framework-neutral; situ ships a Litestar adapter (the reference) and a Flask one, and a component mounts on either unchanged. It is for Python full-stack and backend developers who reach for hypermedia (htmx, Datastar) and hit the wall where a piece of state wants to live in the browser — a live search, a selection, an open dialog.
situ is a young, alpha library extracted from a research repo (webui); the compiler accepts a bounded dialect of Python and fails closed on anything outside it. See Status below.
The one idea: site each piece of state
Every signal declares its site, and the transport follows from that — you never write a fetch, a target, or an SSE wire.
| Site | Where it lives | What the compiler derives |
|---|---|---|
Local[T] |
the browser | compiled to JS, zero network |
Url[T] |
the query string | a shareable link the server re-renders |
Server[Facade] |
the database | a POST command that morphs one region |
Synced[T] |
a local-first replica | a store reconciled by a sync engine |
The boundary is a compile-time invariant: a client read of Server-sited state is a
CompileError, so database-backed state cannot reach the browser by accident.
Install
pip install situ # the compiler + the Litestar mount
pip install "situ[flask]" # + the Flask (WSGI) mount adapter
pip install "situ[sqlalchemy]" # + the optional SQLAlchemy/Dishka session helpers
Requires Python 3.12+.
Quickstart
A component is two sibling files sharing a stem, so each gets native editor tooling.
counter.py — the signals and handlers:
from situ import Local
count: Local[int] = 0 # client state — compiled to JS, no network
def bump() -> None:
global count # names the local signal this handler writes
count = count + 1
counter.html — real HTML with shorthand reactive attributes:
<div data-region>
<button @click="bump">+1</button>
<strong :text="count"></strong>
</div>
app.py — the controller is one mount call plus the wiring:
from pathlib import Path
import situ
from litestar import Litestar
from litestar.plugins.jinja import JinjaTemplateEngine
from litestar.static_files import create_static_files_router
from litestar.template.config import TemplateConfig
from situ import mount_static_component
HERE = Path(__file__).parent
app = Litestar(
route_handlers=[
mount_static_component(
path="/counter",
stem=HERE / "counter", # counter.py + counter.html
template="page.html", # situ ships a minimal default
meta={"name": "Counter"},
),
# serve the runtime shim the generated island loads from /static/_rt.js
create_static_files_router(path="/static", directories=[situ.static_dir()]),
],
template_config=TemplateConfig(
directory=situ.templates_dir(), engine=JinjaTemplateEngine
),
)
litestar --app app:app run # then open http://localhost:8000/counter
Two rules the runtime enforces: every reactive element lives inside <header> or the
single <div data-region> (where binders are wired at boot), and data-region must be
the first attribute on that <div>.
When a piece of state needs the database, declare it Server[Facade], give the component a
context function and a facade, and switch to mount_component — the same component file,
now with a server seam. mount_tree composes a root component with typed children. To serve
the same compiled component on Flask instead, situ.mount.flask.mount_flask mounts it as a
Blueprint — see examples/flask/.
What's in the box
situ.compiler— the Python→JS compiler (parse_front_end,load_front_end,compile_app,splice_tree) and the site markers. Pure standard library.situ.mount— a framework-neutral mount core (situ.mount.core: dispatch + render + the command/region/feed/window protocol, no framework imported) with thin adapters over it: the Litestar route factories (mount_component,mount_static_component,mount_tree) and a FlaskBlueprintadapter (situ.mount.flask.mount_flask). The Litestar bindings load lazily, so the Flask path imports no Litestar.situ.siting— theSignal/Signals/Sitecontract.situ.infra.templating— a Jinja string-render helper (render_string); and, behind[sqlalchemy],situ.infra.db/situ.infra.di— a SQLAlchemy async engine + Dishka session provider.situ.static_dir()/situ.templates_dir()— paths to the bundled_rt.jsshim and the defaultpage.html, to wire into your Litestar static + template config.
Status
Alpha. The compiler accepts a bounded dialect of Python and rejects the rest with a clear
error; it does not relocate database I/O to the client. Dishka is required only by the
Litestar mount_component, which reads request.state.dishka_container (the [sqlalchemy]
extra ships a ready provider, or wire your own); the Flask adapter takes a plain resolve
callable and needs no Dishka. The story behind the design — fifteen TodoMVC
implementations and a four-way master-detail comparison — lives in the
webui research repo.
License
MIT © Stéphane Fermigier
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file situ-0.1.0.tar.gz.
File metadata
- Download URL: situ-0.1.0.tar.gz
- Upload date:
- Size: 111.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28216e0a023ad2a7dccdf2a7cfc52fbb7b7067de5e74284b86610cf4c1c80f9a
|
|
| MD5 |
97c30c492494e94b1459c2c3b4b5cc37
|
|
| BLAKE2b-256 |
7260582dafcdb0fffa5674c2e61bb3bbbdd8f5a31d7a743804a09a48615336c0
|
File details
Details for the file situ-0.1.0-py3-none-any.whl.
File metadata
- Download URL: situ-0.1.0-py3-none-any.whl
- Upload date:
- Size: 129.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
605875c8a341f4a3e6ce2505a1d95464120e2f7dfc828e5b2e81e396c4d0dc71
|
|
| MD5 |
76cbd652a6de878921d93d09c4e0c9ea
|
|
| BLAKE2b-256 |
0285c738d4506259b19565b6f3227f4cb27af0eddfad572b1effc7d082d12d39
|