66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
"""Shared helper utilities for Dagster assets."""
|
||
|
||
import json
|
||
|
||
|
||
def safe(val):
|
||
"""Convert non-serialisable values (tuples, lists of dicts, etc.) for JSONB."""
|
||
if isinstance(val, list | dict | tuple):
|
||
return json.dumps(val, default=str)
|
||
return val
|
||
|
||
|
||
def safe_int(val):
|
||
"""Try to cast to int, return None on failure."""
|
||
if val is None:
|
||
return None
|
||
try:
|
||
return int(val)
|
||
except (ValueError, TypeError):
|
||
try:
|
||
return int(float(val))
|
||
except (ValueError, TypeError):
|
||
return None
|
||
|
||
|
||
def md_preview_table(
|
||
rows: list[dict],
|
||
columns: list[tuple[str, str]],
|
||
formatters: dict[str, callable] | None = None,
|
||
) -> str:
|
||
"""Build a markdown table from a list of row dicts.
|
||
|
||
Args:
|
||
rows: List of dictionaries containing row data.
|
||
columns: List of (key, header_label) tuples defining the columns.
|
||
formatters: Optional dict mapping column keys to formatting callables.
|
||
Each callable receives the raw value and returns a display string.
|
||
Columns without a formatter fall back to the raw value or "–".
|
||
"""
|
||
formatters = formatters or {}
|
||
headers = [label for _, label in columns]
|
||
lines = [
|
||
"| " + " | ".join(headers) + " |",
|
||
"| " + " | ".join("---" for _ in headers) + " |",
|
||
]
|
||
for r in rows:
|
||
cells = []
|
||
for key, _ in columns:
|
||
val = r.get(key)
|
||
if key in formatters:
|
||
cells.append(formatters[key](val))
|
||
else:
|
||
cells.append(str(val) if val is not None else "–")
|
||
lines.append("| " + " | ".join(cells) + " |")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def format_euro(val) -> str:
|
||
"""Format an integer as €-prefixed with thousands separators, or '–'."""
|
||
return f"€{val:,}" if val else "–"
|
||
|
||
|
||
def format_area(val) -> str:
|
||
"""Format an integer as m² area, or '–'."""
|
||
return f"{val} m²" if val else "–"
|