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.2" 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]
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()
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'.
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).
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"}, ...]
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.
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)
189def header(title: str, library: str = "default", emoji: str = "", subtitle: str = ""): 190 """Top banner used at the start of every launch() UI.""" 191 color = accent(library) 192 sub = ( 193 f"<div style='font-size:12px;color:{MUTED_FOREGROUND};margin-top:2px;" 194 f"font-family:{FONT_SANS}'>{subtitle}</div>" 195 ) if subtitle else "" 196 return html( 197 f"<h2 style='color:{color};margin-bottom:0;font-weight:700;" 198 f"letter-spacing:-0.01em;font-family:{FONT_SANS}'>{emoji} {title}</h2>{sub}" 199 )
Top banner used at the start of every launch() UI.
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.
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)
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.
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)
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.
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').