Skip to main content

Site your state, derive the wire — a Python→JS component compiler with an explicit client/server seam, for Litestar.

Project description

situ

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 Flask Blueprint adapter (situ.mount.flask.mount_flask). The Litestar bindings load lazily, so the Flask path imports no Litestar.
  • situ.siting — the Signal / Signals / Site contract.
  • 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.js shim and the default page.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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

situ-0.1.0.tar.gz (111.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

situ-0.1.0-py3-none-any.whl (129.8 kB view details)

Uploaded Python 3

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

Hashes for situ-0.1.0.tar.gz
Algorithm Hash digest
SHA256 28216e0a023ad2a7dccdf2a7cfc52fbb7b7067de5e74284b86610cf4c1c80f9a
MD5 97c30c492494e94b1459c2c3b4b5cc37
BLAKE2b-256 7260582dafcdb0fffa5674c2e61bb3bbbdd8f5a31d7a743804a09a48615336c0

See more details on using hashes here.

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

Hashes for situ-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 605875c8a341f4a3e6ce2505a1d95464120e2f7dfc828e5b2e81e396c4d0dc71
MD5 76cbd652a6de878921d93d09c4e0c9ea
BLAKE2b-256 0285c738d4506259b19565b6f3227f4cb27af0eddfad572b1effc7d082d12d39

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page