dashui

DashUI — shared ipywidgets component library for the Dashlibs suite. Import from here in any dash-* package instead of duplicating widget code.

 1"""
 2DashUI — shared ipywidgets component library for the Dashlibs suite.
 3Import from here in any dash-* package instead of duplicating widget code.
 4"""
 5from dashui.components import (
 6    EditableTable,
 7    SourceSelector,
 8    action_button,
 9    card,
10    editable_table,
11    header,
12    html,
13    output_panel,
14    running_list,
15    section,
16    source_selector,
17    status_line,
18)
19from dashui.schema import list_columns, list_columns_safe
20from dashui.theme import (
21    ACCENT_BG,
22    ACCENT_FG,
23    BORDER,
24    BORDER_STRONG,
25    CARD,
26    DANGER,
27    FONT_MONO,
28    FONT_SANS,
29    INFO,
30    MUTED,
31    PRIMARY,
32    SUCCESS,
33    WARNING,
34    accent,
35)
36
37__version__ = "0.2.3"
38__all__ = [
39    "SourceSelector",
40    "EditableTable",
41    "action_button",
42    "card",
43    "editable_table",
44    "header",
45    "html",
46    "output_panel",
47    "running_list",
48    "section",
49    "source_selector",
50    "status_line",
51    "list_columns",
52    "list_columns_safe",
53    "accent",
54    "PRIMARY",
55    "SUCCESS",
56    "DANGER",
57    "WARNING",
58    "INFO",
59    "BORDER",
60    "BORDER_STRONG",
61    "CARD",
62    "MUTED",
63    "ACCENT_BG",
64    "ACCENT_FG",
65    "FONT_SANS",
66    "FONT_MONO",
67]
@dataclass
class SourceSelector:
234@dataclass
235class SourceSelector:
236    """
237    The UC Table / DataFrame variable / SQL Query picker used by every
238    Dashlibs UI that reads from Databricks.
239
240    Usage::
241        src = source_selector()
242        ui = card([src.toggle, src.box, ...])
243        kind, value = src.value()
244    """
245    toggle: object
246    box: object
247    table_input: object
248    df_input: object
249    sql_input: object
250
251    def value(self) -> tuple[str, str]:
252        """Returns (kind, value) where kind is 'table' | 'dataframe' | 'sql'."""
253        if self.toggle.value == "UC Table":
254            return "table", self.table_input.value.strip()
255        if self.toggle.value == "DataFrame variable":
256            return "dataframe", self.df_input.value.strip()
257        return "sql", self.sql_input.value.strip()
258
259    def resolve_df(self):
260        """Resolve the selected source to a Spark DataFrame (for direct use in core classes)."""
261        kind, value = self.value()
262        if kind == "dataframe":
263            import IPython
264            shell = IPython.get_ipython()
265            df = shell.user_ns.get(value) if shell else None
266            if df is None:
267                raise ValueError(f"Variable '{value}' not found")
268            return df
269        from pyspark.sql import SparkSession
270        spark = SparkSession.getActiveSession()
271        if kind == "table":
272            return spark.table(value)
273        return spark.sql(value)

The UC Table / DataFrame variable / SQL Query picker used by every Dashlibs UI that reads from Databricks.

Usage:: src = source_selector() ui = card([src.toggle, src.box, ...]) kind, value = src.value()

SourceSelector( toggle: object, box: object, table_input: object, df_input: object, sql_input: object)
toggle: object
box: object
table_input: object
df_input: object
sql_input: object
def value(self) -> tuple[str, str]:
251    def value(self) -> tuple[str, str]:
252        """Returns (kind, value) where kind is 'table' | 'dataframe' | 'sql'."""
253        if self.toggle.value == "UC Table":
254            return "table", self.table_input.value.strip()
255        if self.toggle.value == "DataFrame variable":
256            return "dataframe", self.df_input.value.strip()
257        return "sql", self.sql_input.value.strip()

Returns (kind, value) where kind is 'table' | 'dataframe' | 'sql'.

def resolve_df(self):
259    def resolve_df(self):
260        """Resolve the selected source to a Spark DataFrame (for direct use in core classes)."""
261        kind, value = self.value()
262        if kind == "dataframe":
263            import IPython
264            shell = IPython.get_ipython()
265            df = shell.user_ns.get(value) if shell else None
266            if df is None:
267                raise ValueError(f"Variable '{value}' not found")
268            return df
269        from pyspark.sql import SparkSession
270        spark = SparkSession.getActiveSession()
271        if kind == "table":
272            return spark.table(value)
273        return spark.sql(value)

Resolve the selected source to a Spark DataFrame (for direct use in core classes).

@dataclass
class EditableTable:
339@dataclass
340class EditableTable:
341    """
342    An add/remove-row key-value grid — the pattern Databricks itself uses for
343    job parameters, cluster tags, and environment variables, instead of a
344    single free-text "key=value, key=value" field.
345
346    Usage::
347        tbl = editable_table(["Key", "Value"], placeholders={"Key": "AWS_REGION"})
348        ui = card([tbl.widget, ...])
349        rows = tbl.values()  # [{"Key": "AWS_REGION", "Value": "us-east-1"}, ...]
350    """
351    widget: object
352    add_row: object   # callable(b=None) -> None, wired as a Button.on_click handler too
353    values: object     # callable() -> list[dict[str, str]]

An add/remove-row key-value grid — the pattern Databricks itself uses for job parameters, cluster tags, and environment variables, instead of a single free-text "key=value, key=value" field.

Usage:: tbl = editable_table(["Key", "Value"], placeholders={"Key": "AWS_REGION"}) ui = card([tbl.widget, ...]) rows = tbl.values() # [{"Key": "AWS_REGION", "Value": "us-east-1"}, ...]

EditableTable(widget: object, add_row: object, values: object)
widget: object
add_row: object
values: object
def action_button(text: str, style: str = 'primary', emoji: str = ''):
296def action_button(text: str, style: str = "primary", emoji: str = ""):
297    """style in primary|success|warning|danger|info — matches the Databricks
298    button variants. `emoji` is kept for API compatibility but not used by
299    any dash-* package — Databricks buttons are plain text, no glyph."""
300    w = _require_widgets()
301    label = f"{emoji} {text}".strip()
302    btn = w.Button(description=label, layout=w.Layout(height="32px", padding="0 14px", width="auto"))
303    btn.add_class(f"dashui-btn-{style or 'default'}")
304    return btn

style in primary|success|warning|danger|info — matches the Databricks button variants. emoji is kept for API compatibility but not used by any dash-* package — Databricks buttons are plain text, no glyph.

def card(children, padding: str = '16px'):
222def card(children, padding: str = "16px"):
223    """Bordered, shadowed VBox container — the outer shell for every launch() UI."""
224    w = _require_widgets()
225    global _STYLE_INJECTED
226    body = [_global_style(), *children] if not _STYLE_INJECTED else list(children)
227    _STYLE_INJECTED = True
228    box = w.VBox(body, layout=w.Layout(padding=padding))
229    box.add_class("dashui-card")
230    box.add_class("dashui-root")
231    return box

Bordered, shadowed VBox container — the outer shell for every launch() UI.

def editable_table( columns: list[str], placeholders: dict[str, str] | None = None, initial_rows: int = 1) -> EditableTable:
356def editable_table(columns: list[str], placeholders: dict[str, str] | None = None, initial_rows: int = 1) -> EditableTable:
357    w = _require_widgets()
358    placeholders = placeholders or {}
359    row_entries: list[tuple[object, dict]] = []  # (row_box, {col: Text widget})
360
361    header_row = w.HBox(
362        [w.HTML(f"<div class='dashui-table-header'>{col}</div>") for col in columns] + [w.HTML("", layout=w.Layout(width="32px"))]
363    )
364    rows_box = w.VBox([])
365
366    def _make_row():
367        cells = {col: w.Text(placeholder=placeholders.get(col, ""), layout=w.Layout(width="auto", flex="1")) for col in columns}
368        remove_btn = w.Button(description="✕", layout=w.Layout(width="32px", height="28px"), tooltip="Remove row")
369        remove_btn.add_class("dashui-btn-info")
370        row_box = w.HBox([cells[c] for c in columns] + [remove_btn])
371        row_box.add_class("dashui-table-row")
372
373        def on_remove(_b):
374            row_entries[:] = [(rb, c) for rb, c in row_entries if rb is not row_box]
375            rows_box.children = tuple(rb for rb, _ in row_entries)
376
377        remove_btn.on_click(on_remove)
378        return row_box, cells
379
380    def add_row(_b=None):
381        row_box, cells = _make_row()
382        row_entries.append((row_box, cells))
383        rows_box.children = tuple(rb for rb, _ in row_entries)
384
385    for _ in range(initial_rows):
386        add_row()
387
388    add_btn = action_button("Add row", style="info", emoji="+")
389    add_btn.on_click(add_row)
390
391    def values() -> list[dict]:
392        return [
393            {col: cells[col].value.strip() for col in columns}
394            for _, cells in row_entries
395            if any(cells[col].value.strip() for col in columns)
396        ]
397
398    table = w.VBox([header_row, rows_box, add_btn])
399    table.add_class("dashui-table")
400    return EditableTable(widget=table, add_row=add_row, values=values)
def html(text: str):
57def html(text: str):
58    w = _require_widgets()
59    return w.HTML(text)
def output_panel():
307def output_panel():
308    """Standard scrollable output area for run/profile results and errors."""
309    w = _require_widgets()
310    out = w.Output(layout=w.Layout(padding="12px"))
311    out.add_class("dashui-output")
312    return out

Standard scrollable output area for run/profile results and errors.

def running_list(formatter):
315def running_list(formatter):
316    """
317    A live-updating numbered list display, the pattern used for 'added entities'
318    / 'added relationships' style accumulators.
319
320    Usage::
321        items = []
322        out, render = running_list(lambda i, item: f"{i}. {item['name']}")
323        items.append({"name": "Customer"})
324        render(items)
325    """
326    w = _require_widgets()
327    out = w.Output(layout=w.Layout(padding="8px 12px"))
328    out.add_class("dashui-output")
329
330    def render(items: list):
331        with out:
332            out.clear_output()
333            for i, item in enumerate(items, 1):
334                print(formatter(i, item))
335
336    return out, render

A live-updating numbered list display, the pattern used for 'added entities' / 'added relationships' style accumulators.

Usage:: items = [] out, render = running_list(lambda i, item: f"{i}. {item['name']}") items.append({"name": "Customer"}) render(items)

def section(title: str):
205def section(title: str):
206    """Step/section divider, styled like the datapal-access card label convention."""
207    return html(f"<div class='dashui-section'>{title}</div>")

Step/section divider, styled like the datapal-access card label convention.

def source_selector(label: str = 'Source:') -> SourceSelector:
276def source_selector(label: str = "Source:") -> SourceSelector:
277    w = _require_widgets()
278    toggle = w.ToggleButtons(options=["UC Table", "DataFrame variable", "SQL Query"], description=label)
279    table_input = w.Text(placeholder="catalog.schema.table", description="Table:")
280    df_input = w.Text(placeholder="df", description="Variable:")
281    sql_input = w.Textarea(placeholder="SELECT * FROM ...", description="SQL:", rows=3)
282    box = w.VBox([table_input])
283
284    def on_change(change):
285        if change["new"] == "UC Table":
286            box.children = [table_input]
287        elif change["new"] == "DataFrame variable":
288            box.children = [df_input]
289        else:
290            box.children = [sql_input]
291
292    toggle.observe(on_change, names="value")
293    return SourceSelector(toggle, box, table_input, df_input, sql_input)
def status_line(text: str, kind: str = 'info'):
210def status_line(text: str, kind: str = "info"):
211    """One-line status message: kind in success|error|warning|info. A small
212    solid dot carries the color instead of a colorful emoji — closer to how
213    Databricks' own job/cluster status indicators read."""
214    color = {"success": SUCCESS, "error": DANGER, "warning": WARNING, "info": MUTED_FOREGROUND}.get(kind, MUTED_FOREGROUND)
215    return html(
216        f"<span style='font-family:{FONT_SANS};color:#1B3139'>"
217        f"<span style='display:inline-block;width:6px;height:6px;border-radius:50%;"
218        f"background:{color};margin-right:7px'></span>{text}</span>"
219    )

One-line status message: kind in success|error|warning|info. A small solid dot carries the color instead of a colorful emoji — closer to how Databricks' own job/cluster status indicators read.

def list_columns(table: str) -> list[str]:
 6def list_columns(table: str) -> list[str]:
 7    """Return column names for a UC table, without loading any data."""
 8    from pyspark.sql import SparkSession
 9    spark = SparkSession.getActiveSession()
10    return [f.name for f in spark.table(table).schema.fields]

Return column names for a UC table, without loading any data.

def list_columns_safe(table: str) -> list[str]:
13def list_columns_safe(table: str) -> list[str]:
14    """Like list_columns, but returns [] instead of raising — for UI dropdowns."""
15    try:
16        return list_columns(table)
17    except Exception:
18        return []

Like list_columns, but returns [] instead of raising — for UI dropdowns.

def accent(library: str) -> str:
72def accent(library: str) -> str:
73    """Look up the accent color for a Dashlibs package name (e.g. 'dashsynthetic')."""
74    return ACCENTS.get(library, ACCENTS["default"])

Look up the accent color for a Dashlibs package name (e.g. 'dashsynthetic').

PRIMARY = '#FF3621'
SUCCESS = '#2E7D32'
DANGER = '#C62828'
WARNING = '#B36B00'
INFO = '#0E6BA8'
BORDER = '#DCE0E2'
BORDER_STRONG = '#C7CCD1'
CARD = '#FFFFFF'
MUTED = '#F3F4F5'
ACCENT_BG = '#FFF1EC'
ACCENT_FG = '#B33B1E'
FONT_SANS = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
FONT_MONO = "'Roboto Mono', 'SFMono-Regular', Consolas, monospace"