Textualize/textual · error · RowDoesNotExist

Row index {row_index!r} is not valid.

Error message

Row index {row_index!r} is not valid.

What it means

Raised by DataTable.get_row_at when the integer row_index fails is_valid_row_index (negative beyond -row_count, or >= row_count). Index-based row access reflects the current visual/sort order, so out-of-range indices raise RowDoesNotExist. get_row_at then delegates to get_row, so the row must exist at that position.

Source

Thrown at src/textual/widgets/_data_table.py:1026

        ]
        return ordered_row

    def get_row_at(self, row_index: int) -> list[CellType]:
        """Get the values from the cells in a row at a given index. This will
        return the values from a row based on the rows _current position_ in
        the table.

        Args:
            row_index: The index of the row.

        Returns:
            A list of the values contained in the row.

        Raises:
            RowDoesNotExist: If there is no row with the given index.
        """
        if not self.is_valid_row_index(row_index):
            raise RowDoesNotExist(f"Row index {row_index!r} is not valid.")
        row_key = self._row_locations.get_key(row_index)
        return self.get_row(row_key)

    def get_row_index(self, row_key: RowKey | str) -> int:
        """Return the current index for the row identified by row_key.

        Args:
            row_key: The row key to find the current index of.

        Returns:
            The current index of the specified row key.

        Raises:
            RowDoesNotExist: If the row key does not exist.
        """
        if row_key not in self._row_locations:
            raise RowDoesNotExist(f"No row exists for row_key={row_key!r}")
        return self._row_locations.get(row_key)

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Check bounds first: 0 <= index < table.row_count (or -row_count <= index < row_count).
  2. Prefer key-based access (get_row with the key from add_row) when order can change.
  3. Re-read table.row_count immediately before access after any add/remove/sort/clear.
  4. Catch textual.widgets._data_table.RowDoesNotExist as a defensive fallback.

Example fix

// before
row = table.get_row_at(current_index)

// after
if table.is_valid_row_index(current_index):
    row = table.get_row_at(current_index)
else:
    row = None
Defensive patterns

Strategy: validation

Validate before calling

row = table.get_row_at(i) if table.is_valid_row_index(i) else None

Try / catch

from textual.widgets._data_table import RowDoesNotExist
try:
    row = table.get_row_at(i)
except RowDoesNotExist:
    row = None

Prevention

When it happens

Trigger: Calling get_row_at with an index >= table.row_count, a negative index smaller than -row_count, or using an index captured before sorting/filtering/row removal changed the ordering.

Common situations: Iterating with a stale row count after async row deletion; assuming a fixed row count after re-sorting; off-by-one errors when iterating range(table.row_count + 1); using indices while a cursor/selection callback races with updates.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/d22c3e3c11616bdb. Report an issue: GitHub.