Skip to content

Golit

Reactive data apps that actually ship.

A high-performance reactive DAG framework for building data apps in Python. Golit maps your data dependencies once, then on every interaction recomputes only the nodes that changed — not your whole script.

Get started Why Golit?


Documentation: you're reading it  ·  Source: github.com/boadzie/golit  ·  License: Apache-2.0

Golit closes the gap between rapid data prototyping (Streamlit, Dash) and production-grade, horizontally scalable web apps. It keeps the single-file, write-Python-get-a-UI ergonomics of prototyping tools, but replaces their wasteful execution model with a precise reactive engine backed by a Rust compute kernel.

The key idea

Conventional data frameworks re-execute the entire script on every interaction. Move a slider and the framework re-loads the dataset, re-runs every transform, and re-renders every widget — even the 90% of the page that didn't change. Latency scales with script size, not with the size of the change.

Golit treats your app as a dependency graph, not a script:

  1. An input changes and its node is marked dirty.
  2. Dirtiness propagates downstream to only the transitively affected nodes.
  3. Those nodes re-execute in topological order; everything else is a memo hit.
  4. Only the UI fragments bound to recomputed nodes are re-rendered and swapped into the live DOM.

The result is updates whose cost is proportional to the change, not the program.

Features

Reactive by default

Plain Python functions become graph nodes. Dependencies are inferred from the function signature — no wiring, no callbacks.

Rust kernel

Dirty tracking, topological scheduling, and propagation run in a Rust + PyO3 kernel. The graph walk on every interaction never pays interpreter overhead.

Polars data, zero-copy

DataFrames stay Python-side as Arrow buffers. Only node ids and u64 content hashes cross the FFI boundary — never the data.

HTMX fragment transport

The wire format is the final UI: pre-rendered HTML/SVG fragments swapped by HTMX. No client framework, no hydration, minimal payload.

Charts, batteries included

Lets-Plot static SVG out of the box, plus auto-detected interactive Plotly / Altair / Bokeh and an AnyChart helper.

Maps & GIS

Return a GeoDataFrame for a native MapLibre GL choropleth, or a rioxarray/xarray array for a raster layer — plus DuckDB spatial SQL. A map is a reactive view like any other.

Scales horizontally

A single process needs nothing extra. Add Redis and a sticky load balancer to fan a fleet of workers out — server state stays worker-local by design.

Requirements

Golit requires Python 3.11+. The core install pulls in Litestar, Polars, Lets-Plot, and Uvicorn. Optional features (interactive charts, SQL nodes, Redis fan-out) are extras you opt into.

Installation

pip install golit                 # core
pip install "golit[charts]"       # interactive Plotly / Altair / Bokeh
pip install "golit[sql]"          # DuckDB SQL nodes over Polars frames
pip install "golit[gis]"          # native MapLibre maps from GeoDataFrames
pip install "golit[gis-raster]"   # raster maps from rioxarray/xarray arrays
pip install "golit[redis]"        # Redis fan-out for multi-worker

Example

Create a file app.py:

import polars as pl
from golit import App, create_app, slider, upload
from golit.charts import aes, geom_bar, ggplot, ggsize

app = App(title="Sales Explorer")

SAMPLE = pl.DataFrame({"region": ["North", "South", "East", "West"], "revenue": [120, 90, 70, 40]})


@app.source
def data(file=upload("Upload CSV")) -> pl.DataFrame:
    return SAMPLE if file is None else pl.read_csv(file)  # renders before any upload


@app.reactive
def filtered(data: pl.DataFrame, threshold: int = slider(0, 100, default=20)) -> pl.DataFrame:
    return data.filter(pl.col("revenue") > threshold)  # re-runs only when data/threshold change


@app.view
def chart(filtered: pl.DataFrame):
    return ggplot(filtered, aes("region", "revenue")) + geom_bar(stat="identity") + ggsize(640, 360)


application = create_app(app)  # an ASGI app

Run it

golit run app.py

Open http://127.0.0.1:8000. Moving the slider dirties threshold → filtered → chart. The data node is never touched; only the chart fragment is re-rendered and swapped.

That selective recompute is the whole point

A view that depends only on data (say, a dataset overview) is not re-rendered on a slider move. The framework does the minimum work the change requires — and nothing more.

Why Golit?

Streamlit Dash Golit
Execution unit Full script Callback Dirty subgraph
Update cost ∝ script size ∝ callback ∝ change
Wire format WebSocket diff JSON HTML fragments
Data engine Pandas Pandas Polars (Rust)
Charting Plotly/Altair (JS) Plotly (JS) Lets-Plot → static SVG
Maps / GIS pydeck / st.map dash-leaflet (JS) MapLibre from a GeoDataFrame
Reactive core Python rerun Python Rust (PyO3)
Horizontal scale Hard Manual Redis-backed, native

Read the full comparison

Where to next

Tutorial

The hands-on User Guide — build up from your first app to charts, components, layout, and SQL.

Concepts

How it works — the reactive model, the four tiers, and how a change flows end-to-end.

Reference

The API reference, generated from the source.