Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 84 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
# hotdata-marimo

Marimo UI helpers for [Hotdata](https://hotdata.dev): run SQL from a notebook, browse catalog metadata, and render results as tables.
Marimo widgets for [Hotdata](https://hotdata.dev): run SQL, browse catalogs, load managed databases, and display results in notebooks.

## Features
Requires Python 3.10+, [Marimo](https://marimo.io/), and [hotdata-runtime](https://github.com/hotdata-dev/hotdata-runtime) (installed automatically).

- **Workspace-aware setup** — build a `HotdataClient` from environment variables, or use `workspace_selector_from_env()` to choose a workspace interactively when no workspace is pinned.
- **Connection health** — show a compact status callout with API, workspace, and optional sandbox context.
- **Catalog browsing** — browse Hotdata connections, schemas, tables, and columns from Marimo UI controls.
- **SQL editor widget** — run SQL against Hotdata, cache the latest successful result, and render results in downstream reactive cells.
- **Native `mo.sql` engine** — register `HotdataMarimoEngine` so Marimo SQL cells can execute through a live `HotdataClient` with `engine=client`.
- **Result display helpers** — render query results, recent results, and run history as notebook-friendly UI.
- **Marimo UI aliases** — importing `hotdata_marimo` attaches helpers such as `mo.ui.hotdata_sql_editor` and `mo.ui.hotdata_table_browser` for discoverability.
## Supported widgets

Importing `hotdata_marimo` registers `mo.ui.hotdata_*` aliases for discoverability.

| Widget | Function | Notes |
|--------|----------|-------|
| SQL editor | `hm.sql_editor(client)` | Returns `.ui` and `.result` |
| Table browser | `hm.table_browser(client)` | Browse connections, schemas, tables, columns |
| Managed databases panel | `hm.databases_panel(client)` | Create catalogs and load parquet files |
| Managed database writer | `hm.managed_database_writer(client)` | Lower-level create/load UI |
| Workspace selector | `hm.workspace_selector_from_env()` | Pick workspace when `HOTDATA_WORKSPACE` is unset |
| Connection picker | `hm.connection_picker(client)` | Dropdown of workspace connections |
| Connection status | `hm.connection_status(client)` | API / workspace health callout |
| Connections panel | `hm.connections_panel(client)` | Status callout plus connection list |
| Query result | `hm.query_result(result)` | Render a `QueryResult` as a table |
| Recent results | `hm.recent_results(client)` | Browse past query results |
| Run history | `hm.run_history(client)` | Recent query runs |

Each widget also has a `mo.ui.hotdata_*` alias (e.g. `mo.ui.hotdata_sql_editor`). Native Marimo SQL cells are supported via `hm.register_hotdata_sql_engine()` and `mo.sql(..., engine=client)`.

## Install

```bash
uv pip install hotdata-marimo
# or: pip install hotdata-marimo
pip install hotdata-marimo
```

Set `HOTDATA_API_KEY`. Optionally set `HOTDATA_WORKSPACE`, `HOTDATA_API_URL`, or `HOTDATA_SANDBOX`.

## Connect

```python
import hotdata_marimo as hm

client = hm.from_env()
```

Requires Python 3.10+, **Marimo**, and [**hotdata-runtime**](https://github.com/hotdata-dev/hotdata-runtime) (Hotdata SDK + runtime/session semantics — pulled in automatically when you `pip install hotdata-marimo`).
If `HOTDATA_WORKSPACE` is unset, pick a workspace interactively:

```python
ws = hm.workspace_selector_from_env()
client = ws.client
```

## Environment
## SQL editor widget

| Variable | Required | Description |
|----------|----------|-------------|
| `HOTDATA_API_KEY` | Yes | API key for the Hotdata API |
| `HOTDATA_API_URL` | No | API base URL (default: `https://api.hotdata.dev`) |
| `HOTDATA_WORKSPACE` | No | Workspace id; if unset, the first active workspace is used |
| `HOTDATA_SANDBOX` | No | Sandbox session id, passed through to the SDK |
Run SQL in one cell; show results in the next. Marimo only renders what you **`return`**.

## Minimal notebook
**Cell 1 — editor**

```python
import marimo as mo
Expand All @@ -41,25 +62,30 @@ editor = hm.sql_editor(client, default_sql="SELECT 1 AS ok")
return editor.ui
```

**Cell 2 — result**

```python
return hm.query_result(editor.result)
```

Importing `hotdata_marimo` registers discoverability aliases on Marimo’s UI namespace, so you can also use `mo.ui.hotdata_sql_editor`, `mo.ui.hotdata_table_browser`, `mo.ui.hotdata_query_result`, and `mo.ui.hotdata_connection_status`.
Click **Run on Hotdata** after changing SQL. The editor caches the last successful result so downstream cells do not re-query on every refresh.

Use `hm.connection_status(client)` (or `mo.ui.hotdata_connection_status(client)`) for a small API/workspace health callout.
## Native Marimo SQL cells

## Marimo SQL Cells
Register the Hotdata engine once, then pass `engine=client` to `mo.sql`. Hotdata appears as **Hotdata** in the SQL connection picker.

Register the Hotdata SQL engine once during setup, then pass a `HotdataClient` to Marimo SQL cells:
**Setup cell**

```python
import marimo as mo
import hotdata_marimo as hm

hm.register_hotdata_sql_engine()
client = hm.from_env()
```

**SQL cell**

```python
_df = mo.sql(
"""
Expand All @@ -69,46 +95,57 @@ _df = mo.sql(
)
```

The engine also exposes Hotdata catalog metadata to Marimo's data-source UI. Hotdata connections are labeled **Hotdata** in the SQL connection picker.
![Marimo SQL cell with Hotdata selected in the database connections picker](docs/images/mo-sql-hotdata-connection.png)

## Browse tables

```python
browser = hm.table_browser(client)
return browser.ui
```

Pick a connection, schema, and table to inspect columns. Use `browser.selected_table` in downstream cells.

## Two-cell pattern
## Managed databases

Keep the editor in one cell and consume `editor.result` in another. The editor caches the last successful run so downstream cells do not re-query the API on every refresh; click **Run on Hotdata** again after you change SQL. While a query is running, a Marimo status spinner is shown.
Create a Hotdata-owned catalog and load a parquet file from the notebook:

Marimo only shows **what you `return` from a cell**. Calling `mo.vstack(...)` or `hm.query_result(...)` without returning it produces no visible output.
```python
panel = hm.databases_panel(client)
return panel
```

See `examples/demo.py` for a full runnable notebook flow.
Or use the lower-level writer API:

## Examples
```python
writer = hm.managed_database_writer(client)
return writer.ui
```

- `examples/demo.py` — tabbed explorer with workspace selection, connection health, recent results (selectable table), run history, and a native `mo.sql` cell.
## Other helpers

Run locally (single-user machine):
See [Supported widgets](#supported-widgets) for the full list. Quick examples:

```bash
uv run marimo edit examples/demo.py --no-token
```python
return hm.connection_status(client)
return hm.connections_panel(client)
return hm.recent_results(client).ui
return hm.run_history(client)
```

On a **shared or networked host**, omit `--no-token` and use the access token printed in the terminal URL. Without it, anyone who can reach the Marimo port can run queries against your Hotdata workspace.
## Demo notebook

## Layout
```bash
uv run marimo edit examples/demo.py --no-token
```

This repo is intentionally thin: **API client, env helpers, and result models** live in **hotdata-runtime**; **hotdata-marimo** only adds Marimo widgets (`sql_editor`, `table_browser`, `display` for tables/status/history, `workspace_selector`). Import `HotdataClient` / `QueryResult` / `from_env` from **`hotdata_marimo`** or directly from **`hotdata_runtime`**.
`examples/demo.py` combines workspace selection, catalog browsing, managed databases, query history, and a native `mo.sql` cell.

## Development

This package depends on [**hotdata-runtime**](https://github.com/hotdata-dev/hotdata-runtime) (PyPI name `hotdata-runtime`). Development uses **uv**; keep a sibling checkout at `../hotdata-runtime` so the lockfile resolves the runtime from disk (see `[tool.uv.sources]` in `pyproject.toml`).

```bash
uv sync --locked
uv run pytest
marimo edit examples/demo.py --no-token
```

To pin **hotdata-runtime** from Git instead of the sibling path, remove the `[tool.uv.sources]` block, set the dependency line as needed, and run `uv lock` again.

For a **publishable** `uv.lock` (CI that only clones this repo), remove `[tool.uv.sources]`, point `hotdata-runtime` at PyPI or `git+https://…`, then `uv lock`.

The **`[project] name`** in [hotdata-runtime](https://github.com/hotdata-dev/hotdata-runtime) `pyproject.toml` is **`hotdata-runtime`** and the import package is **`hotdata_runtime`**.

Use **`--no-token`** for local development so the editor does not redirect to `/auth/login` (session auth is easy to hit with a global Marimo config). For a public or shared machine, omit it and use the printed URL with an access token instead.
See [hotdata-runtime](https://github.com/hotdata-dev/hotdata-runtime) for the underlying API client.
Binary file added docs/images/mo-sql-hotdata-connection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 14 additions & 5 deletions examples/demo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import marimo

__generated_with = "0.23.5"
__generated_with = "0.23.6"
app = marimo.App()


Expand Down Expand Up @@ -36,34 +36,43 @@ def _(hm, mo, os):
def _(hm, workspace):
client = workspace.client
status = hm.connections_panel(client)
db_writer = hm.managed_database_writer(client)
recent = hm.recent_results(client, limit=20)
history = hm.run_history(client, limit=10)
return client, history, recent, status
return client, db_writer, history, recent, status


@app.cell
def _(mo):
mo.md(r"""
## HotData explorer
Use the tabs below to switch between workspaces, connection status, recent results, and run history.
Use the tabs below to switch between workspaces, connections, managed databases,
recent results, and run history.

On a shared or networked host, run Marimo **without** `--no-token` and open the printed URL
with its access token so only you can use this notebook.
""")
return


@app.cell
def _(db_writer):
databases_tab = db_writer.tab_ui
return (databases_tab,)


@app.cell
def _(recent):
recent_tab = recent.tab_ui
return (recent_tab,)


@app.cell
def _(history, mo, recent_tab, status, workspace):
def _(databases_tab, history, mo, recent_tab, status, workspace):
mo.ui.tabs({
"Workspaces": workspace.ui,
"Connections": status,
"Databases": databases_tab,
"Recent results": recent_tab,
"Run history": history,
})
Expand All @@ -73,7 +82,7 @@ def _(history, mo, recent_tab, status, workspace):
@app.cell
def _(client, mo):
_df = mo.sql(
"""
f"""
SELECT 1 AS example_value
""",
engine=client
Expand Down
14 changes: 14 additions & 0 deletions hotdata_marimo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

from hotdata_runtime import HotdataClient, QueryResult, from_env

from hotdata_marimo.databases import (
ManagedDatabaseWriter,
databases_panel,
managed_database_writer,
)
from hotdata_marimo.display import (
RecentResults,
connection_status,
Expand All @@ -31,20 +36,25 @@
"HotdataClient",
"HotdataMarimoEngine",
"QueryResult",
"ManagedDatabaseWriter",
"RecentResults",
"SqlEditor",
"TableBrowser",
"WorkspaceSelector",
"connection_picker",
"connection_status",
"connections_panel",
"databases_panel",
"from_env",
"hotdata_connection_picker",
"hotdata_databases_panel",
"hotdata_managed_database_writer",
"hotdata_query_result",
"hotdata_recent_results",
"hotdata_sql_editor",
"hotdata_table_browser",
"hotdata_workspace_selector",
"managed_database_writer",
"query_result",
"recent_results",
"register_hotdata_sql_engine",
Expand All @@ -60,6 +70,8 @@
hotdata_table_browser = table_browser
hotdata_query_result = query_result
hotdata_connection_picker = connection_picker
hotdata_databases_panel = databases_panel
hotdata_managed_database_writer = managed_database_writer
hotdata_workspace_selector = workspace_selector_from_env
hotdata_recent_results = recent_results

Expand All @@ -73,6 +85,8 @@ def register_mo_ui_hotdata_aliases() -> None:
mo.ui.hotdata_query_result = hotdata_query_result # type: ignore[attr-defined]
mo.ui.hotdata_connection_status = connection_status # type: ignore[attr-defined]
mo.ui.hotdata_connection_picker = hotdata_connection_picker # type: ignore[attr-defined]
mo.ui.hotdata_databases_panel = hotdata_databases_panel # type: ignore[attr-defined]
mo.ui.hotdata_managed_database_writer = hotdata_managed_database_writer # type: ignore[attr-defined]
mo.ui.hotdata_workspace_selector = hotdata_workspace_selector # type: ignore[attr-defined]
mo.ui.hotdata_recent_results = hotdata_recent_results # type: ignore[attr-defined]

Expand Down
Loading
Loading