Skip to content

fix(data-editor): don't crash appending a row to an empty row-oriented editor - #10651

Closed
winklemad wants to merge 2 commits into
marimo-team:mainfrom
winklemad:fix/data-editor-empty-append
Closed

fix(data-editor): don't crash appending a row to an empty row-oriented editor#10651
winklemad wants to merge 2 commits into
marimo-team:mainfrom
winklemad:fix/data-editor-empty-append

Conversation

@winklemad

Copy link
Copy Markdown
Contributor

This pull request was authored by a coding agent.

📝 Summary

Closes #10650

_apply_positional_edit_row_oriented crashed with IndexError when appending a new row to an empty row-oriented editor: it built the new row from data[0], which raises on an empty list. This is reachable from the UI by deleting all rows and then adding one.

When there is no existing row to read column names from, this falls back to the editor's schema (and always includes the edited column), so appending the first row works. The column-oriented path already handled the empty case.

📋 Pre-Review Checklist

  • I have discussed large / public-API changes via an issue — n/a, small bug fix.
  • Any AI-generated code has been reviewed line-by-line by me and I stand by it.

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation/docstrings updated where applicable.
  • Tests added (empty-append + remove-all-then-add replay).

…d editor

Appending a positional edit to an empty row-oriented data editor raised
IndexError because the new row's columns were read from data[0], which
does not exist when there are no rows yet. Derive the columns from the
schema when available and always include the edited column, mirroring the
column-oriented path. This also fixes the remove-all-then-add edit replay.
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview Aug 28, 2026 4:04am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a crash when appending the first row in an empty row-oriented data editor.

Changes:

  • Adds fallback logic for empty row-oriented appends.
  • Adds regression tests for empty and remove-all-then-add scenarios.
  • Critical issue: replay via _convert_value can still drop existing columns because no schema is preserved or passed.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Summary
tests/_plugins/ui/_impl/test_data_editor.py Adds regression coverage for empty-row appends.
marimo/_plugins/ui/_impl/data_editor.py Updates empty-row append handling; requires preserving or passing the original editor schema and testing _convert_value.
Suppressed comments (3)

marimo/_plugins/ui/_impl/data_editor.py:471

  • This still appends only one row for every rowIdx >= len(data). For example, an empty row-oriented input with rowIdx: 1 reaches data[1] after this append and raises IndexError; the column-oriented path extends through the requested index. Extend by rowIdx - len(data) + 1 rows (or reject non-contiguous indices) so the guarded branch is correct for all out-of-range positional edits.
        new_row: dict[str, Any] = {col: None for col in columns}
        new_row.setdefault(edit["columnId"], None)
        data.append(new_row)

marimo/_plugins/ui/_impl/data_editor.py:471

  • This fallback only adds the column from the first edit. If one edit batch contains multiple cells for the first new row, the next column enters the existing-row path and data[0][columnId] raises KeyError. The UI's row-add replay emits one positional edit per column, so remove-all-then-add with a multi-column row still crashes; ensure the target row contains the column before reading/updating it and add that regression case.
        new_row: dict[str, Any] = {col: None for col in columns}
        new_row.setdefault(edit["columnId"], None)
        data.append(new_row)

marimo/_plugins/ui/_impl/data_editor.py:466

  • nw.Schema exposes its column names through names() (as used elsewhere in this codebase), not keys(). This makes the new schema fallback raise AttributeError before appending, so the added _with_schema regression test fails; use schema.names() here.
            columns = list(schema.keys())

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread marimo/_plugins/ui/_impl/data_editor.py Outdated
Comment on lines +465 to +466
elif schema is not None:
columns = list(schema.keys())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winklemad if the PR is ready; please address this comment, before i start a review

@kirangadhave
kirangadhave marked this pull request as ready for review August 27, 2026 18:31
@kirangadhave
kirangadhave marked this pull request as draft August 27, 2026 18:31
The row-oriented replay only fixed the narrowest empty-append crash and still
lost data: after a remove-all, a rebuilt row kept only the edited column; a
multi-column rebuild hit KeyError on the second column (it read data[0][col]);
and a non-contiguous rowIdx past the end raised IndexError.

Capture the columns before edits run (so a remove-all doesn't lose them), thread
them into the positional handler, extend the data through the requested index,
and read the original value with .get. Also use nw.Schema.names() (the idiomatic
narwhals accessor). Fixes the dropped-column / KeyError / IndexError cases and
covers them via the helper and end-to-end through _convert_value.
@winklemad

Copy link
Copy Markdown
Contributor Author

Thanks — Copilot's critical concern was right, and the first pass only fixed the narrowest case. Verified against the real replay path (_convert_valueapply_edits with no schema, self._data a plain list), the previous version had four problems:

scenario (remove-all / empty, then add) before now
add one column [{'A':'x'}] — column B dropped [{'A':'x','B':None}]
rebuild full row (1 edit/col) KeyError 'B' [{'A':'x','B':'y'}]
add to truly-empty editor KeyError 'B' [{'A':'x','B':'y'}]
non-contiguous rowIdx=2 IndexError 3 rows, no crash

Fixed in 4cd1e13:

  • Capture the column names in _apply_edits_row_oriented before the edits run, so a remove that empties the list doesn't lose them, and thread them into the positional handler.
  • Extend the data through the requested index (while len(data) <= rowIdx) instead of a single append, mirroring the column-oriented path — fixes the IndexError.
  • Read the original value with .get instead of data[0][col] — fixes the KeyError.
  • Switched schema.keys()schema.names() (the idiomatic narwhals accessor; .keys() happened to work since Schema subclasses OrderedDict, but .names() is what's used elsewhere).

Tests: fixed the assertion that pinned the dropped-column behavior, and added multi-column, non-contiguous, and an end-to-end _convert_value case (per your note to cover it through the editor, not just the helper). Full test_data_editor.py green.

@kirangadhave this should be ready for your review now.

@Light2Dark

Copy link
Copy Markdown
Member

Thanks @winklemad , closing this in favour of #10662

@Light2Dark Light2Dark closed this Aug 28, 2026
@winklemad

Copy link
Copy Markdown
Contributor Author

Thanks @Light2Dark — no worries at all. #10662's broader approach of making the whole replay path robust to structural changes is the better factoring than patching the row-oriented handler in isolation, so it makes sense to land that instead. Glad the four-failure-mode breakdown (dropped column / KeyError / IndexError) was useful as a checklist for it. Happy to give #10662 a review if that would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

data_editor: IndexError appending a row to an empty row-oriented editor

4 participants