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
| Call | Renders |
|---|---|
bz.title / header / text / markdown | headings 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 |
| Widget | Returns |
|---|---|
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.