Live data sources (polling)¶
Most golit nodes recompute when you change an input. But some data changes on its own — a Google Sheet a colleague is editing, an API, a file a job rewrites. @app.poll turns that into a first-class live source: golit fetches it on an interval and, when the content changes, pushes the re-rendered views to every open browser over SSE. You write plain views; they update themselves.
The shape¶
import polars as pl
import golit.ui as ui
from golit import App, create_app
app = App(title="Live")
@app.poll("sheet", interval=3) # fetch every 3s
async def sheet() -> pl.DataFrame: # the fetch — return the latest data
return await fetch_csv(SHEET_URL)
@app.view
def table(sheet) -> str: # depends on the polled source by name
if sheet is None: # None until the first fetch lands
return ui.spinner(label="Loading…")
return ui.table(sheet)
application = create_app(app)
@app.poll(name, interval=...) registers a source node named name plus a background poller. The decorated function is the fetch (sync or async); golit runs it every interval seconds, and your views depend on name like any other source.
Only changes cost anything¶
Each fetch is fingerprinted (an md5 of its content). If the fingerprint is unchanged, golit does nothing — no re-render, nothing on the wire. When it changes, golit publishes an invalidation, force-recomputes the source, and pushes only the changed view fragments to every connected client. That's the same server-side-invalidation path streaming sources and background jobs use; polling is just a convenient producer for it.
sequenceDiagram
participant P as poll loop
participant F as your fetch fn
participant PS as PubSub
participant SSE as SSE channel
participant B as Browser
loop every interval
P->>F: fetch()
F-->>P: data
P->>P: hash — changed?
alt changed
P->>PS: publish Invalidation(name)
PS->>SSE: deliver
SSE->>B: re-rendered view fragments
else unchanged
P->>P: do nothing
end
end
Notes¶
- Sync or async fetch. An
async deffetch is awaited; a plaindefruns in a worker thread, so a blocking request never stalls the event loop. Noneuntil the first fetch. The source isNoneuntil the first poll lands — have views render a loading state.- Errors are survived. A fetch that raises is logged and retried on the next tick; the last good value stays on screen.
- Per worker process. Like
@app.stream, one poller runs per worker — under multiple workers each polls. For a single shared fetch, pin the poller to one worker.
Full example¶
examples/live_sheets/app.py streams a public Google Sheet (or any .csv URL): a poller fetches the CSV export SSRF-safely, and a table plus an auto-inferred chart update live as the sheet changes. Point GOLIT_SHEET_URL at your own "anyone with the link can view" sheet and edit it to watch the dashboard move.
A polled source composes with anything a view can return. examples/live_great_table/app.py feeds the same live sheet into a Great Tables GT object — a formatted display table that redraws itself over SSE, no client code (pip install "golit[tables]").
Reference¶
App.poll— the decorator.