Metadata-Version: 2.4
Name: situ
Version: 0.2.0
Summary: Site your state, derive the wire — a Python→JS component compiler with an explicit client/server seam, for Litestar.
Keywords: litestar,hypermedia,compiler,reactive,signals,web-ui,frontend,python-to-javascript
Author: Stéphane Fermigier
Author-email: Stéphane Fermigier <sfermigier@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Litestar
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Code Generators
Classifier: Typing :: Typed
Requires-Dist: litestar>=2.22
Requires-Dist: jinja2>=3.1
Requires-Dist: flask>=3.0 ; extra == 'flask'
Requires-Dist: attrs>=23.0 ; extra == 'model-adapters'
Requires-Dist: msgspec>=0.18 ; extra == 'model-adapters'
Requires-Dist: pydantic>=2.0 ; extra == 'model-adapters'
Requires-Dist: sqlalchemy>=2.0.50 ; extra == 'sqlalchemy'
Requires-Dist: aiosqlite>=0.20.0 ; extra == 'sqlalchemy'
Requires-Dist: dishka>=1.6 ; extra == 'sqlalchemy'
Requires-Dist: greenlet>=3.0 ; extra == 'sqlalchemy'
Requires-Python: >=3.12
Project-URL: Documentation, https://situ.hop3.abilian.com/
Project-URL: Homepage, https://situ.hop3.abilian.com/
Project-URL: Issues, https://github.com/sfermigier/situ/issues
Project-URL: Repository, https://github.com/sfermigier/situ
Provides-Extra: flask
Provides-Extra: model-adapters
Provides-Extra: sqlalchemy
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/situ-wordmark.svg" alt="situ" width="220">
</p>

<p align="center"><em>Site your state, derive the wire.</em></p>

---

**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 declaration 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](https://litestar.dev) 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 programme (the
> [webui](https://github.com/sfermigier/webui) repository); the compiler accepts a
> **bounded** dialect of Python and fails *closed* on anything outside it.
> See [Status](#status) and the [known limits](docs/src/project/status.md).

## 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 re-renders one region |
| `Synced[T]` | *(reserved)* | a local-first replica — design stage |

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

```bash
pip install situ                   # the compiler + the Litestar mount
pip install "situ[flask]"          # + the Flask (WSGI) adapter
pip install "situ[sqlalchemy]"     # + the SQLAlchemy/Dishka session helpers
pip install "situ[model-adapters]" # + attrs/msgspec/pydantic model sources for declui
```

Requires Python 3.12+.

## Sixty seconds of situ

A component is **two sibling files sharing a stem**, so each gets native editor tooling.

`counter.py` — the signals and handlers:

```python
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:

```html
<div data-region>
  <button @click="bump">+1</button>
  <strong :text="count"></strong>
</div>
```

`app.py` — the controller is one mount call plus the wiring:

```python
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
    ),
)
```

```bash
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>` (the two roots where binders are wired), and `data-region` is
the **first attribute** on that `<div>`.

## When state needs the database

Declare the site, and the seam follows:

```python
search: Local[str] = ""       # live search — compiled to JS, zero network
filter: Url[str] = "all"      # a shareable link the server re-renders
issues: Server[IssuesFacade]  # the store: a facade in handlers, rows in the template


async def close(id):          # `await` makes this a server command:
    await issues.set_status(id, "closed")   # → POST /cmd/close/{id} → region re-render
```

A handler containing `await` becomes a POST command; every other handler compiles to
client JS. The [Server components guide](docs/src/getting-started/server-components.md)
walks the wiring, and the [tutorial](docs/src/tutorial/index.md) builds a complete issue
tracker this way in six parts — live search, filters, selection, commands, a create
dialog — with **zero hand-written JavaScript** and a generated island of ~7 KB.

## Or generate the UI from a model: declui

For CRUD-shaped screens, skip the component too. **declui** turns a typed model into a
working form, list, master-detail, or server-backed tracker — at compile time, onto the
same seam:

```python
@dataclass
class Sample:
    title: str                                              # str  → text input
    shirt: Annotated[Shirt, Field()] = Shirt.medium         # Enum → <select>
    price: Annotated[Decimal, Field()] = Decimal("10.50")   # Decimal → decimal input
    need_by: Annotated[date | None, Field()] = None         # date → date picker

app = Litestar(route_handlers=[mount_model(path="/sample", screen=Screen(model=Sample)), ...])
```

Conditional predicates (`editable="rating > 50"`) compile to client binders; `@action`
methods become gated command buttons; models may be dataclasses, attrs, msgspec,
Pydantic, or SQLAlchemy. In the capstone example, an 8-line `Screen` stands in for the
311 lines of hand-written components in the equivalent demo.
[declui documentation →](docs/src/declui/index.md)

## Litestar or Flask

The mount has a portable core (`situ.mount.core` — dispatch, render, the
command/region/feed/window protocol, no framework imported) with thin adapters over it:

- **Litestar** (the reference): `mount_component` / `mount_static_component` /
  `mount_tree`, with per-request DI via Dishka.
- **Flask** (WSGI): `situ.mount.flask.mount_flask` returns an ordinary `Blueprint`; a
  plain `resolve=lambda: store` callable replaces the DI container, and the request path
  imports zero Litestar. See [`examples/flask/`](examples/flask/app.py).

## Documentation

Full documentation lives in [`docs/`](docs/) (a [Zensical](https://zensical.org) site —
`make docs-serve` to browse it locally at http://localhost:8000):

- **[Quickstart](docs/src/getting-started/quickstart.md)** — the counter, explained.
- **[Tutorial](docs/src/tutorial/index.md)** — build an issue tracker in six parts; every
  listing compiles and is browser-verified.
- **[Concepts](docs/src/concepts/sites-and-the-seam.md)** — sites & the seam, handlers &
  commands, the compiled dialect, composition, the wire protocol.
- **[Cheat sheet](docs/src/reference/cheat-sheet.md)** — the whole authoring surface on
  one page; plus the full [binder](docs/src/reference/binders.md),
  [API](docs/src/reference/api.md), and [error](docs/src/reference/errors.md) references.
- **[situ vs. …](docs/src/getting-started/comparisons.md)** — React, Vue, Svelte, htmx,
  Datastar, LiveView, Eliom and the research lineage.
- **For AI coding assistants**: [`llms.txt`](docs/src/llms.txt) (index) and
  [`llms-full.txt`](docs/src/llms-full.txt) — a single-file, self-contained reference
  with verified samples and the rules an agent must follow.

## Demos & examples

Everything documented runs, and everything that runs is browser-verified (the e2e suite
fails on any console error):

```bash
uv run litestar --app demos.app:app run       # 17 demos behind one gallery
uv run uvicorn examples.declui.app:app        # the 13-example declui tour
uv run --with flask flask --app examples/flask/app.py run   # the Flask adapter
```

<p align="center">
  <img src="docs/src/assets/demo-issues.png" alt="The flagship demo: a master-detail issue tracker" width="720">
</p>

The flagship (`/issues`, above) is a five-component master-detail tracker whose entire
client is a **117-line generated island**; across all thirty shipped apps the islands
measure 6–13 KB over a ~540-line shared shim. Each demo page shows its own source, its
signal→transport table, and the island it serves.

## 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` — the framework-neutral mount core plus the Litestar route factories and
  the Flask `Blueprint` adapter. Litestar bindings load lazily.
- `situ.declui` — the model→UI generator (`Field`, `Screen`, `zones`, `action`,
  `mount_model`).
- `situ_ui` — a component kit: 24 components (Dialog, Combobox, DataGrid, Menu, …), a
  `ui-*` CSS class contract, interactions compiled from Python.
- `situ.siting` — the `Signal` / `Signals` / `Site` contract; `situ.infra` — Jinja
  string rendering and, behind `[sqlalchemy]`, an async engine + Dishka session provider.
- `python -m situ.check` — static component-tree resolution for your lint loop.

## 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. `Synced` is a reserved site — the
design names it, and the implementation is future work. Dishka is required only by the
Litestar `mount_component`; the Flask adapter takes a plain callable. The full list of
limits is in [Status & roadmap](docs/src/project/status.md).

The design is research-backed and the record is public: fifteen TodoMVC implementations
and a four-way master-detail comparison in the
[webui repository](https://github.com/sfermigier/webui), a per-round design log in
[`notes/`](notes/), and a technical report in
[`docs/papers/TR-001.md`](docs/papers/TR-001.md).

## License

[MIT](LICENSE) © Stéphane Fermigier
