Skip to content

exp_data_editor API#

exp_data_editor mirrors the intentionally small contract of marimo.ui.data_editor: supply tabular data, decide which columns are editable, and read the edited rows from the widget value.

It accepts marimo's documented eager-data forms: dataframe-like inputs, scalars (shown in a value column), records, and mappings of columns. It also accepts label, on_change, column_sizing_mode, pagination, and page_size. pagination=True is client-side pagination for eager data. For large datasets, use pagination="server" with a Python page source, or pagination="remote" with RemotePageSource when running in Marimo WASM. Passing a PageSource or RemotePageSource selects its matching large-data mode by default.

import marimo as mo
from lumut import exp_data_editor

editor = mo.ui.anywidget(exp_data_editor(
    [{"name": "Ada", "notes": "Text can wrap over multiple lines."}],
    editable_columns=["notes"],
    wrapped_columns=["notes"],
    wrap_text=True,
    auto_row_height=True,
    max_row_height=160,
))

editor

Server and remote pagination synchronize compact edit patches; they do not eagerly materialize a full editor.value. Remote endpoints must return either a row array or { "rows": [...], "row_count": N } for ?offset=N&limit=N requests, and accept PATCH { "patches": [...] }. Set wrap_text=False to truncate text in every column, or auto_row_height=False to keep every row at estimated_row_height; wrapped_columns selects which columns may wrap when wrapping is enabled.

Bases: AnyWidget

Edit a small-to-medium tabular dataset in any AnyWidget runtime.

The widget mirrors the intentionally small public contract of marimo.ui.data_editor: it accepts tabular data, controls which columns may be edited, and synchronizes the fully edited rows through value. Text rows are measured lazily as they enter the viewport. Heights are capped by max_row_height so a single long value cannot dominate the scroll geometry.

Wrap this widget with mo.ui.anywidget in marimo. Its value is then available through the wrapper's value attribute.

Source code in lumut/exp_data_editor.py
def __init__(
    self,
    data: Sequence[Mapping[str, Any]] | Mapping[str, Sequence[Any]] | Any | None = None,
    *,
    label: str = "",
    on_change: Callable[[list[Row]], None] | None = None,
    editable_columns: EditableColumns = "all",
    wrapped_columns: WrappedColumns = "all",
    wrap_text: bool = True,
    auto_row_height: bool = True,
    column_sizing_mode: ColumnSizingMode = None,
    pagination: PaginationMode = None,
    page_size: int | None = None,
    source: PageSource | RemotePageSource | None = None,
    max_row_height: int = 240,
    estimated_row_height: int = 34,
    width: str = "100%",
    height: int = 450,
) -> None:
    if max_row_height < estimated_row_height:
        raise ValueError("max_row_height must be at least estimated_row_height")
    if height <= 0:
        raise ValueError("height must be positive")
    if column_sizing_mode not in (None, "auto", "fit"):
        raise ValueError("column_sizing_mode must be 'auto', 'fit', or None")
    if page_size is not None and page_size <= 0:
        raise ValueError("page_size must be positive")

    if data is not None and source is not None:
        raise ValueError("pass either data or source, not both")
    mode = _pagination_mode(pagination, source)
    if mode == "remote" and not isinstance(source, RemotePageSource):
        raise ValueError("pagination=\"remote\" requires RemotePageSource")
    if mode == "server" and source is None:
        raise ValueError("pagination=\"server\" requires a PageSource")

    source_rows: list[Row] = []
    initial_page: list[Row] = []
    row_count = 0
    if mode == "server":
        row_count = source.count()  # type: ignore[union-attr]
        initial_page = [dict(row) for row in source.page(offset=0, limit=page_size or 25)]  # type: ignore[union-attr]
        columns = list(initial_page[0]) if initial_page else []
    elif mode == "remote":
        row_count = source.row_count  # type: ignore[union-attr]
        columns = list(source.columns)  # type: ignore[union-attr]
    else:
        source_rows = _to_rows(data if data is not None else [])
        row_count = len(source_rows)
        columns = list(source_rows[0]) if source_rows else _column_names(data)
        estimated_bytes = _estimate_transport_bytes(source_rows)
        if len(source_rows) > 50_000 or estimated_bytes > 8 * 1024 * 1024:
            warnings.warn(
                "eager data will send "
                f"{len(source_rows):,} rows (about {_format_bytes(estimated_bytes)}) to the browser; "
                "recommended eager maximum is 50,000 rows and 8 MiB. "
                "Use pagination=\"server\" or \"remote\" for larger datasets.",
                RuntimeWarning,
                stacklevel=2,
            )

    _validate_columns(editable_columns, columns, "editable_columns")
    _validate_columns(wrapped_columns, columns, "wrapped_columns")
    self._source = source
    self._pagination_mode = mode

    super().__init__(
        data=source_rows if mode in ("none", "client") else [],
        initial_rows=source_rows if mode in ("none", "client") else [],
        value=[dict(row) for row in source_rows] if mode in ("none", "client") else [],
        editable_columns="all" if editable_columns == "all" else list(editable_columns),
        wrapped_columns="all" if wrapped_columns == "all" else list(wrapped_columns),
        wrap_text=wrap_text,
        auto_row_height=auto_row_height,
        column_sizing_mode=column_sizing_mode,
        pagination=mode != "none",
        pagination_mode=mode,
        page_size=page_size or 25,
        row_count=row_count,
        page_rows=initial_page or (source_rows[: page_size or 25] if mode == "client" else []),
        remote_url=source.url if isinstance(source, RemotePageSource) else "",
        remote_patch_url=(source.patch_url or source.url) if isinstance(source, RemotePageSource) else "",
        columns=columns,
        field_types=_infer_field_types(initial_page or source_rows, columns),
        label=label,
        width=width,
        height=height,
        max_row_height=max_row_height,
        estimated_row_height=estimated_row_height,
    )
    self._on_change = on_change
    if on_change is not None:
        self.observe(self._notify_change, names="value")