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]
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()
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'.
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).
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"}, ...]
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.
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)
189def header(title: str, library: str = "default", emoji: str = "", subtitle: str = ""): 190 """Top banner used at the start of every launch() UI. `emoji` is kept for 191 API compatibility but not used by any dash-* package — Databricks' own 192 page headers are plain text, no decorative glyph.""" 193 color = accent(library) 194 prefix = f"{emoji} " if emoji else "" 195 sub = ( 196 f"<div style='font-size:12px;color:{MUTED_FOREGROUND};margin-top:2px;" 197 f"font-family:{FONT_SANS}'>{subtitle}</div>" 198 ) if subtitle else "" 199 return html( 200 f"<h2 style='color:{color};margin-bottom:0;font-weight:700;" 201 f"letter-spacing:-0.01em;font-family:{FONT_SANS}'>{prefix}{title}</h2>{sub}" 202 )
Top banner used at the start of every launch() UI. emoji is kept for
API compatibility but not used by any dash-* package — Databricks' own
page headers are plain text, no decorative glyph.
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.
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)
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.
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)
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.
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.
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.
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').