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.1"
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:
226@dataclass
227class SourceSelector:
228    """
229    The UC Table / DataFrame variable / SQL Query picker used by every
230    Dashlibs UI that reads from Databricks.
231
232    Usage::
233        src = source_selector()
234        ui = card([src.toggle, src.box, ...])
235        kind, value = src.value()
236    """
237    toggle: object
238    box: object
239    table_input: object
240    df_input: object
241    sql_input: object
242
243    def value(self) -> tuple[str, str]:
244        """Returns (kind, value) where kind is 'table' | 'dataframe' | 'sql'."""
245        if self.toggle.value == "UC Table":
246            return "table", self.table_input.value.strip()
247        if self.toggle.value == "DataFrame variable":
248            return "dataframe", self.df_input.value.strip()
249        return "sql", self.sql_input.value.strip()
250
251    def resolve_df(self):
252        """Resolve the selected source to a Spark DataFrame (for direct use in core classes)."""
253        kind, value = self.value()
254        if kind == "dataframe":
255            import IPython
256            shell = IPython.get_ipython()
257            df = shell.user_ns.get(value) if shell else None
258            if df is None:
259                raise ValueError(f"Variable '{value}' not found")
260            return df
261        from pyspark.sql import SparkSession
262        spark = SparkSession.getActiveSession()
263        if kind == "table":
264            return spark.table(value)
265        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]:
243    def value(self) -> tuple[str, str]:
244        """Returns (kind, value) where kind is 'table' | 'dataframe' | 'sql'."""
245        if self.toggle.value == "UC Table":
246            return "table", self.table_input.value.strip()
247        if self.toggle.value == "DataFrame variable":
248            return "dataframe", self.df_input.value.strip()
249        return "sql", self.sql_input.value.strip()

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

def resolve_df(self):
251    def resolve_df(self):
252        """Resolve the selected source to a Spark DataFrame (for direct use in core classes)."""
253        kind, value = self.value()
254        if kind == "dataframe":
255            import IPython
256            shell = IPython.get_ipython()
257            df = shell.user_ns.get(value) if shell else None
258            if df is None:
259                raise ValueError(f"Variable '{value}' not found")
260            return df
261        from pyspark.sql import SparkSession
262        spark = SparkSession.getActiveSession()
263        if kind == "table":
264            return spark.table(value)
265        return spark.sql(value)

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

@dataclass
class EditableTable:
329@dataclass
330class EditableTable:
331    """
332    An add/remove-row key-value grid — the pattern Databricks itself uses for
333    job parameters, cluster tags, and environment variables, instead of a
334    single free-text "key=value, key=value" field.
335
336    Usage::
337        tbl = editable_table(["Key", "Value"], placeholders={"Key": "AWS_REGION"})
338        ui = card([tbl.widget, ...])
339        rows = tbl.values()  # [{"Key": "AWS_REGION", "Value": "us-east-1"}, ...]
340    """
341    widget: object
342    add_row: object   # callable(b=None) -> None, wired as a Button.on_click handler too
343    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 = ''):
288def action_button(text: str, style: str = "primary", emoji: str = ""):
289    """style in primary|success|warning|danger|info — matches the Databricks button variants."""
290    w = _require_widgets()
291    label = f"{emoji} {text}".strip()
292    btn = w.Button(description=label, layout=w.Layout(height="32px", padding="0 14px", width="auto"))
293    btn.add_class(f"dashui-btn-{style or 'default'}")
294    return btn

style in primary|success|warning|danger|info — matches the Databricks button variants.

def card(children, padding: str = '16px'):
214def card(children, padding: str = "16px"):
215    """Bordered, shadowed VBox container — the outer shell for every launch() UI."""
216    w = _require_widgets()
217    global _STYLE_INJECTED
218    body = [_global_style(), *children] if not _STYLE_INJECTED else list(children)
219    _STYLE_INJECTED = True
220    box = w.VBox(body, layout=w.Layout(padding=padding))
221    box.add_class("dashui-card")
222    box.add_class("dashui-root")
223    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:
346def editable_table(columns: list[str], placeholders: dict[str, str] | None = None, initial_rows: int = 1) -> EditableTable:
347    w = _require_widgets()
348    placeholders = placeholders or {}
349    row_entries: list[tuple[object, dict]] = []  # (row_box, {col: Text widget})
350
351    header_row = w.HBox(
352        [w.HTML(f"<div class='dashui-table-header'>{col}</div>") for col in columns] + [w.HTML("", layout=w.Layout(width="32px"))]
353    )
354    rows_box = w.VBox([])
355
356    def _make_row():
357        cells = {col: w.Text(placeholder=placeholders.get(col, ""), layout=w.Layout(width="auto", flex="1")) for col in columns}
358        remove_btn = w.Button(description="✕", layout=w.Layout(width="32px", height="28px"), tooltip="Remove row")
359        remove_btn.add_class("dashui-btn-info")
360        row_box = w.HBox([cells[c] for c in columns] + [remove_btn])
361        row_box.add_class("dashui-table-row")
362
363        def on_remove(_b):
364            row_entries[:] = [(rb, c) for rb, c in row_entries if rb is not row_box]
365            rows_box.children = tuple(rb for rb, _ in row_entries)
366
367        remove_btn.on_click(on_remove)
368        return row_box, cells
369
370    def add_row(_b=None):
371        row_box, cells = _make_row()
372        row_entries.append((row_box, cells))
373        rows_box.children = tuple(rb for rb, _ in row_entries)
374
375    for _ in range(initial_rows):
376        add_row()
377
378    add_btn = action_button("Add row", style="info", emoji="+")
379    add_btn.on_click(add_row)
380
381    def values() -> list[dict]:
382        return [
383            {col: cells[col].value.strip() for col in columns}
384            for _, cells in row_entries
385            if any(cells[col].value.strip() for col in columns)
386        ]
387
388    table = w.VBox([header_row, rows_box, add_btn])
389    table.add_class("dashui-table")
390    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():
297def output_panel():
298    """Standard scrollable output area for run/profile results and errors."""
299    w = _require_widgets()
300    out = w.Output(layout=w.Layout(padding="12px"))
301    out.add_class("dashui-output")
302    return out

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

def running_list(formatter):
305def running_list(formatter):
306    """
307    A live-updating numbered list display, the pattern used for 'added entities'
308    / 'added relationships' style accumulators.
309
310    Usage::
311        items = []
312        out, render = running_list(lambda i, item: f"{i}. {item['name']}")
313        items.append({"name": "Customer"})
314        render(items)
315    """
316    w = _require_widgets()
317    out = w.Output(layout=w.Layout(padding="8px 12px"))
318    out.add_class("dashui-output")
319
320    def render(items: list):
321        with out:
322            out.clear_output()
323            for i, item in enumerate(items, 1):
324                print(formatter(i, item))
325
326    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):
202def section(title: str):
203    """Step/section divider, styled like the datapal-access card label convention."""
204    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:
268def source_selector(label: str = "Source:") -> SourceSelector:
269    w = _require_widgets()
270    toggle = w.ToggleButtons(options=["UC Table", "DataFrame variable", "SQL Query"], description=label)
271    table_input = w.Text(placeholder="catalog.schema.table", description="Table:")
272    df_input = w.Text(placeholder="df", description="Variable:")
273    sql_input = w.Textarea(placeholder="SELECT * FROM ...", description="SQL:", rows=3)
274    box = w.VBox([table_input])
275
276    def on_change(change):
277        if change["new"] == "UC Table":
278            box.children = [table_input]
279        elif change["new"] == "DataFrame variable":
280            box.children = [df_input]
281        else:
282            box.children = [sql_input]
283
284    toggle.observe(on_change, names="value")
285    return SourceSelector(toggle, box, table_input, df_input, sql_input)
def status_line(text: str, kind: str = 'info'):
207def status_line(text: str, kind: str = "info"):
208    """One-line colored status message: kind in success|error|warning|info."""
209    color = {"success": SUCCESS, "error": DANGER, "warning": WARNING, "info": MUTED_FOREGROUND}.get(kind, MUTED_FOREGROUND)
210    prefix = {"success": "✅", "error": "❌", "warning": "⚠️", "info": "ℹ️"}.get(kind, "")
211    return html(f"<span style='color:{color};font-family:{FONT_SANS}'>{prefix} {text}</span>")

One-line colored status message: kind in success|error|warning|info.

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"