Metadata-Version: 2.4
Name: situ
Version: 0.1.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: Homepage, https://situ.hop3.abilian.com/
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 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 repo
> ([webui](https://github.com/sfermigier/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

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

```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>` (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](https://github.com/sfermigier/webui).

## License

[MIT](LICENSE) © Stéphane Fermigier
