App & nodes¶
App¶
The blueprint. Collects node definitions from the decorators and resolves the dependency graph.
App
¶
Source code in python/golit/app.py
chat_handlers
property
¶
Registered chat message handlers, keyed by channel (None = all).
streams
property
¶
Registered video frame producers, keyed by stream name.
shared_streams
property
¶
Names of streams registered with shared=True (one producer, fanned out).
frame_handlers
property
¶
Registered browser-camera frame processors, keyed by camera name.
audio_handlers
property
¶
Registered browser-mic clip handlers, keyed by recorder name.
pollers
property
¶
Registered live polled sources, keyed by node name → (fetch fn, interval seconds).
poll_cache
property
¶
The latest value of each polled source; the background poller writes it, the source node reads it (per worker process).
source
¶
Register a source node (brings data in — read a file, query a DB,
return a sample frame). May depend on inputs. Returns fn unchanged.
reactive
¶
Register a reactive node — a pure transform over its upstream nodes
and inputs. Re-runs only when one of those changes. Returns fn unchanged.
view
¶
Register a view node — a renderable leaf whose return value Golit
turns into a UI fragment. Re-renders only when an input changes. Returns
fn unchanged.
on_message
¶
Register a handler for incoming chat messages on channel (or all
channels when None). Without a handler a channel simply relays every
message to the room; with one, the handler owns the message and responds
via the :class:~golit.server.chat.MessageContext it's given::
@app.on_message("room")
async def handle(msg, ctx):
await ctx.broadcast(msg.text, author=msg.author) # relay
if msg.text.startswith("/bot"):
await ctx.reply("beep boop", author="Bot") # to sender only
Usable bare (@app.on_message) or with a channel (@app.on_message("room")).
The handler may be sync or async.
Source code in python/golit/app.py
stream
¶
Register a video frame producer named name, shown by
:func:golit.ui.webcam. The decorated function returns an iterator of frames —
JPEG bytes (e.g. from cv2.imencode) or (H, W, 3) uint8 RGB arrays
(encoded for you) — and each request starts a fresh stream that Golit pushes as an
MJPEG (multipart/x-mixed-replace) response off the event loop::
@app.stream("camera")
def camera():
cap = cv2.VideoCapture(0)
try:
while True:
ok, frame = cap.read()
if not ok:
break
# ... run your detector and draw on `frame` ...
yield cv2.imencode(".jpg", frame)[1].tobytes()
finally:
cap.release()
Sync or async (async def + yield) producers both work; sync frames are
pulled in a worker thread so a blocking camera read or model never stalls the loop.
With shared=True the producer runs once no matter how many viewers connect:
a single background pull keeps the latest frame and fans it out to every <img>,
so N viewers of one camera don't open N devices. The producer starts on the first
viewer and its finally runs when the last leaves. Leave it False (the default)
for synthetic feeds or a genuinely per-viewer source.
Source code in python/golit/app.py
on_frame
¶
Register a per-frame processor for the browser-camera view named name,
shown by :func:golit.ui.camera. Each frame the visitor's webcam captures is sent up
(over a WebSocket), decoded to an (H, W, 3) uint8 RGB array, passed to the handler,
and the value it returns — an RGB array or JPEG bytes — is sent back and displayed::
@app.on_frame("detector")
def detect(frame): # frame: (H, W, 3) uint8 RGB
# ... run your model and draw on a copy of `frame` ...
return frame # annotated RGB array (or JPEG bytes)
Sync or async (async def) handlers both work; sync ones (and every JPEG
decode/encode) run in a worker thread so a heavy model never stalls the loop. One frame
is in flight at a time — the client waits for each result — so a slow handler simply
lowers the frame rate instead of building a backlog. The mirror of :meth:stream, which
produces frames on the server; here the browser is the camera and the server transforms.
Source code in python/golit/app.py
on_audio
¶
Register a handler for clips recorded by the browser-mic view named name,
shown by :func:golit.ui.recorder. When the visitor stops recording, the clip is
uploaded (over a WebSocket) as 16-bit PCM WAV bytes and passed to the handler.
Whatever it returns is shown back in the recorder::
import wave, io
@app.on_audio("note")
def transcribe(wav: bytes): # 16-bit PCM WAV bytes
with wave.open(io.BytesIO(wav)) as w: # stdlib — no ffmpeg needed
seconds = w.getnframes() / w.getframerate()
return f"<p>{seconds:.1f}s recorded</p>" # HTML/renderable, shown in place
A renderable return (string, DataFrame, a :mod:golit.ui component, …) is rendered and
displayed; returning bytes instead sends audio back for playback (an echo, a TTS
reply). Sync or async handlers both work — sync ones run in a worker thread so a heavy
transcribe never stalls the loop. The audio mirror of :meth:on_frame.
Source code in python/golit/app.py
poll
¶
Register a live polled source named name — external data that changes on
its own (a Google Sheet, an API, a file). The decorated function fetches the data;
Golit runs it every interval seconds in the background, and when the result's
content hash changes it updates the source and pushes the dependent views to every
connected browser over SSE — the same server-side-invalidation path streaming sources
use, so you write a plain @app.view and it just updates::
@app.poll("sheet", interval=3)
async def sheet() -> pl.DataFrame:
return await fetch_csv(SHEET_URL) # whatever fetch you like
@app.view
def table(sheet) -> str: # depends on the polled source by name
return ui.table(sheet) if sheet is not None else ui.spinner(label="Loading…")
The fetch may be sync or async; sync fetches run in a worker thread so a blocking
request never stalls the loop. Until the first fetch lands the source is None —
have views handle that. One poller runs per worker process. A fetch that raises is
logged and retried on the next tick (the last good value stays).
Source code in python/golit/app.py
build
¶
Resolve every parameter to an input/dependency/constant. Raises if a parameter is neither a widget, a known node, nor a defaulted constant.
Source code in python/golit/app.py
new_graph
¶
Build a fresh kernel graph for a session (topology only; state is reset per session).
Source code in python/golit/app.py
Node kinds & definitions¶
NodeKind
¶
NodeDef
dataclass
¶
NodeDef(
id: str,
kind: NodeKind,
fn: Callable[..., Any],
params: list[Param],
deps: list[str] = list(),
target: str | None = None,
)
The blueprint for a single node (resolved deps filled in by App.build).
inspect_params
¶
inspect_params(fn: Callable[..., Any]) -> list[Param]
Extract :class:Param metadata from a function signature.
Source code in python/golit/nodes.py
Session¶
The per-client scheduler driver — a fresh kernel graph + value registry built from the shared App blueprint.
Session
¶
Session(
app: App, *, view_renderer: ViewRenderer | None = None
)
Per-session reactive state: a fresh kernel graph + value registry built
from the shared :class:App blueprint.
Source code in python/golit/engine.py
initial_render
¶
update
¶
Commit an input change and return only the view fragments that changed.
Source code in python/golit/engine.py
refresh
¶
Force-recompute a node (e.g. a streaming source or a shared node) and everything downstream; return the view fragments that changed. This is the server-initiated path that feeds the SSE push channel.
Source code in python/golit/engine.py
control_html
¶
Render an input's HTML control at its current value.