Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
56 changes: 37 additions & 19 deletions examples/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,49 +35,67 @@ def _(hm, mo, os):
@app.cell
def _(hm, workspace):
client = workspace.client
status = hm.connection_status(client)
status = hm.connections_panel(client)
browser = hm.table_browser(client)
editor = hm.sql_editor(
client,
default_sql="SELECT 1 AS ok",
)
recent = hm.recent_results(client, limit=20)
history = hm.run_history(client, limit=10)
return browser, client, editor, history, recent, status, workspace
return browser, client, editor, history, recent, status


@app.cell
def _(browser, editor, mo, recent, status, workspace):
return mo.vstack(
[
workspace.ui,
status,
browser.ui,
editor.ui,
recent.ui,
],
gap=2,
)
def _(mo):
mo.md(r"""
## HotData explorer
Use the tabs below to switch between available workspaces, connection status, dataset browsing, and SQL queries.
""")
return


@app.cell
def _(browser):
# Register connection/table widget deps so Marimo reruns layout cells on change.
if browser._conn_pick is not None:
_ = browser._conn_pick.value
_ = browser.table_pick.value
return


@app.cell
def _(history):
return history
def _(browser):
browser_ui = browser.ui
return (browser_ui,)


@app.cell
def _(editor, hm):
def _(browser_ui, editor, history, mo, recent, status, workspace):
mo.ui.tabs({
"Workspaces": workspace.ui,
"Connections": status,
"Datasets": browser_ui,
"SQL query": editor.ui,
"Recent results": recent.ui,
"Run history": history,
})
return


@app.cell
def _(editor):
# Explicitly touch nested widget values so Marimo reruns this cell on clicks.
_run = editor.run.value
_rerun = editor.rerun.value
_clear = editor.clear.value
return hm.query_result(editor.result), _clear, _rerun, _run
return


@app.cell
def _(hm, recent):
def _(recent):
_selected = recent.pick.value
return hm.query_result(recent.result, label="Recent result"), _selected
return


@app.cell
Expand Down
2 changes: 2 additions & 0 deletions hotdata_marimo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from hotdata_marimo.display import (
RecentResults,
connection_status,
connections_panel,
query_result,
recent_results,
run_history,
Expand All @@ -36,6 +37,7 @@
"WorkspaceSelector",
"connection_picker",
"connection_status",
"connections_panel",
"from_env",
"hotdata_connection_picker",
"hotdata_query_result",
Expand Down
107 changes: 107 additions & 0 deletions hotdata_marimo/_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Shared dropdown option helpers for Marimo UI widgets."""

from __future__ import annotations

from collections.abc import Callable
from typing import Any

import marimo as mo

from hotdata_runtime import HotdataClient


def unique_label_options(
pairs: list[tuple[str, str]],
*,
disambiguate: Callable[[str, str, int], str] | None = None,
) -> dict[str, str]:
"""Build a label→value map, suffixing repeated labels when needed."""
counts: dict[str, int] = {}
options: dict[str, str] = {}
for label, value in pairs:
count = counts.get(label, 0)
counts[label] = count + 1
if count == 0:
key = label
elif disambiguate is not None:
key = disambiguate(label, value, count)
else:
key = f"{label} ({count + 1})"
options[key] = value
return options


def empty_dropdown(
*,
label: str,
message: str,
full_width: bool = True,
):
return mo.ui.dropdown(
options={message: ""},
label=label,
full_width=full_width,
)


def connection_options(conns: list[Any]) -> dict[str, str]:
pairs = [(str(c.name), str(c.id)) for c in conns]
return unique_label_options(
pairs,
disambiguate=lambda label, value, count: f"{label} ({value})",
)


def connection_picker_from_connections(
conns: list[Any],
*,
label: str = "Connection",
full_width: bool = True,
):
if not conns:
return empty_dropdown(
label=label,
message="(no connections)",
full_width=full_width,
)
return mo.ui.dropdown(
options=connection_options(conns),
label=label,
full_width=full_width,
)


def connection_picker(
client: HotdataClient,
*,
label: str = "Connection",
full_width: bool = True,
):
conns = client.connections().list_connections().connections
return connection_picker_from_connections(
conns,
label=label,
full_width=full_width,
)


def resolve_connection_picker(
client: HotdataClient,
*,
label: str = "Connection",
full_width: bool = True,
) -> tuple[Any | None, str | None]:
"""Return ``(dropdown_or_none, implicit_connection_id)`` for table browsers."""
conns = client.connections().list_connections().connections
if not conns:
return None, ""
if len(conns) == 1:
return None, conns[0].id
return (
connection_picker_from_connections(
conns,
label=label,
full_width=full_width,
),
None,
)
58 changes: 41 additions & 17 deletions hotdata_marimo/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,7 @@

from hotdata_runtime import HotdataClient, QueryResult, workspace_health_lines


def _option_map_with_unique_labels(
pairs: list[tuple[str, str]],
) -> dict[str, str]:
counts: dict[str, int] = {}
options: dict[str, str] = {}
for label, value in pairs:
count = counts.get(label, 0)
counts[label] = count + 1
key = label if count == 0 else f"{label} ({count + 1})"
options[key] = value
return options
from hotdata_marimo._options import empty_dropdown, unique_label_options


def query_result(
Expand Down Expand Up @@ -76,11 +65,15 @@ def __init__(self, client: HotdataClient, *, limit: int = 50) -> None:
(f"{r.created_at} · {r.status} · {r.result_id}", r.result_id)
for r in self._results
]
options = _option_map_with_unique_labels(option_pairs)
self.pick = mo.ui.dropdown(
options=options or {"(no results)": ""},
label="Recent results",
full_width=True,
options = unique_label_options(option_pairs)
self.pick = (
empty_dropdown(label="Recent results", message="(no results)")
if not options
else mo.ui.dropdown(
options=options,
label="Recent results",
full_width=True,
)
)

@property
Expand Down Expand Up @@ -150,3 +143,34 @@ def connection_status(client: HotdataClient):
mo.md(f"**API** error — {parts[0]}"),
kind="danger",
)


def connections_panel(client: HotdataClient):
"""Workspace health callout plus a table of configured connections."""
status = connection_status(client)
conns = client.connections().list_connections().connections
if not conns:
return mo.vstack([status, mo.md("_No connections in this workspace._")], gap=1)
rows: list[dict[str, object]] = []
for c in conns:
rows.append(
{
"name": c.name,
"id": c.id,
"source_type": getattr(c, "source_type", None),
}
)
return mo.vstack(
[
status,
mo.ui.table(
rows,
label="Connections",
pagination=True,
page_size=min(10, len(rows)),
selection=None,
max_height=320,
),
],
gap=1,
)
14 changes: 5 additions & 9 deletions hotdata_marimo/sql_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def __init__(
) -> None:
super().__init__(connection, engine_name)
self._connections_cache: list[Any] | None = None
self._connection_id_cache: dict[str, str] | None = None

@property
def source(self) -> str:
Expand Down Expand Up @@ -71,15 +70,12 @@ def _resolve_should_auto_discover(
return True
return value

def _connection_ids(self) -> dict[str, str]:
if self._connection_id_cache is None:
self._connection_id_cache = {
str(c.name): str(c.id) for c in self._connections()
}
return self._connection_id_cache

def _connection_id(self, connection_name: str) -> str | None:
return self._connection_ids().get(connection_name)
try:
return self._connection.connection_id_by_name().get(connection_name)
except RuntimeError as e:
LOGGER.warning("%s", e)
return None
Comment on lines 73 to +78
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this drops the local _connection_id_cache and now calls self._connection.connection_id_by_name() on every invocation. _connection_id is hit from get_schemas, get_tables_in_schema, and get_table_details — each of which can fire several times per render — so unless connection_id_by_name() is cheap or memoized on the runtime side, this will multiply backend calls during catalog browsing. Worth confirming the runtime caches the mapping, or memoize locally (e.g., one cached dict reused alongside _connections_cache). (not blocking)


def _connections(self) -> list[Any]:
if self._connections_cache is None:
Expand Down
Loading
Loading