ObeliskDB docs Home Whitepaper Console

Apps — the data app framework

Build governed data apps in a single Python file, served straight from ObeliskDB — no external runtime, no separate deploy.

obelisk.appkit is a home-grown, Streamlit-style layer. An app is one Python file in the apps/ directory that re-runs top-to-bottom on every interaction: widgets return their current value, display calls emit components, the page re-renders. No callbacks, no state machine.

# apps/sales_dashboard.py
from obelisk import appkit as bz

bz.title("Event explorer")
kind = bz.selectbox("Event kind", ["all", "click", "view", "buy"])
min_day = bz.slider("From day", 0, 29, 0)

where = f"WHERE DAY_NUM >= {int(min_day)}"
if kind != "all":
    where += f" AND KIND = '{kind}'"

r = bz.sql(f"SELECT DAY_NUM, KIND, COUNT(*) AS N FROM DEMO.SALES.EVENTS"
           f" {where} GROUP BY DAY_NUM, KIND ORDER BY DAY_NUM")
bz.metric("Rows", len(r.rows))
bz.line_chart(r, x="DAY_NUM", y="N", series="KIND")
bz.dataframe(r)

Open it from the console's Apps tab, or directly at /app/sales_dashboard. Edit the file and reload — no restart needed.

API

CallRenders
bz.title / header / text / markdownheadings and prose
bz.metric(label, value, delta=None)stat tiles (consecutive ones form a row)
bz.dataframe(result) / bz.table(...)a results grid
bz.bar_chart(result, x=, y=)bar chart (validated palette)
bz.line_chart(result, x=, y=, series=)multi-series line chart
WidgetReturns
bz.selectbox(label, options, default)the selected option
bz.slider(label, min, max, value)the number
bz.text_input(label, default)the text (debounced)
bz.checkbox(label, default)bool
bz.button(label)True only on the run triggered by the click

Data access: bz.sql(query) returns a full query Result (rows, columns, profile — the pruning stats make great metrics); bz.query(q) returns a list of dicts; bz.session() exposes the whole session.

Multipage apps

An app can have pages. Declare pages as functions in the same file — or as paths to .py files next to the app — and call .run():

from obelisk import appkit as bz

bz.title("My app")                 # the shared frame: runs on EVERY page
area = bz.selectbox("Area", [...]) # widgets here are shared across pages

def overview():
    bz.metric("Rows", ...)

def deep_dive():
    bz.line_chart(...)

bz.navigation([
    bz.Page(overview),                      # title derived: "Overview"
    bz.Page(deep_dive, title="Deep dive"),
    bz.Page("more_charts.py", title="More"),  # file-based page
]).run()

The page tabs render under the app title; only the selected page's code runs. Everything emitted before .run() is the frame shared by all pages, and widget state is shared too — a filter picked on one page holds on the next. See apps/care_command_center.py for a four-page example.

Governance applies

Apps query through the shared server session: whatever role the console is using governs app queries too — masking policies mask, row policies filter, denied tables error. There is no app-level bypass; an app can only see what its caller's role can see. Switch the role in the console top bar and reload the app to watch the results change.

Prefer Streamlit?

It works too. Point real Streamlit at ObeliskDB with obelisk.connector (see Python) and run it as any external tool — the same governance still applies.