Skip to content

Conduit

Conduit is how you build interactive UI in Almasix without leaving Python.

Write a component class. Render a Prism template. Sprinkle wire:* attributes in the HTML. Conduit keeps server state and the browser in sync — clicks, forms, typing, the whole dance — over a signed, CSRF-safe wire protocol. Pair it with Alpine.js when you want a little client-side sparkle, and $wire bridges the two worlds.

app/conduit/counter.py
from almasix.conduit import Component
class Counter(Component):
count = 0
def increment(self) -> None:
self.count += 1
def render(self) -> str:
return "conduit.counter"
resources/views/conduit/counter.prism.html
<div>
<h1 conduit:text="count">{{ count }}</h1>
<button type="button" conduit:click="increment">+</button>
</div>
resources/views/layouts/app.prism.html
@conduitScripts
<conduit:counter />

Click the button. Conduit posts a tiny update, runs increment() on the server, and morphs the DOM. The wire:text binding updates instantly from local memo so it feels like JavaScript — even though the truth still lives in Python.

That’s the whole game: server is the source of truth, the browser stays snappy.

Most “interactive” apps today split the world in two: a JSON API on one side, a frontend framework on the other, and a pile of glue in between. Conduit collapses that for the common case.

  • One language for the feature — state, validation, and actions live in a Python class.
  • HTML you already know — wire:click, wire:model, wire:submit. No build step required to get started.
  • Optimistic bindings — wire:text, wire:show, and wire:bind:* update from client memo before the morph lands.
  • Alpine when you want it — $wire lets Alpine read and call into your component without a second state store.
  • Secure by default — CSRF plus signed update URLs (HMAC + expiry). Tampered snapshots get a hard no.

Conduit ships Alpine alongside its script (or you bundle your own). Inside a component, $wire is your component:

resources/views/conduit/counter.prism.html
<div x-data="{ hovering: false }" @mouseenter="hovering = true" @mouseleave="hovering = false">
<p>
Count is <span x-text="$conduit.count"></span>
<span x-show="hovering" x-transition>(hover tip from Alpine)</span>
</p>
<button type="button" @click="$conduit.increment()">+</button>
<button type="button" @click="$conduit.count = 0">Reset locally + sync</button>
</div>

Local Alpine state (hovering) stays in the browser. Server state (count) stays on $wire. They play nicely together — that’s the point.

Install almasix-conduit (or the almasix[conduit] extra). The import path is always:

from almasix.conduit import Component, Conduit, conduit

The service provider registers itself via the almasix.providers entry point. Drop @conduitScripts in your layout, embed with <conduit:name /> or @conduit('name'), and you’re off.