Textualize/textual · error · RowDoesNotExist

Row key {row_key!r} is not valid.

Error message

Row key {row_key!r} is not valid.

What it means

Raised by DataTable.get_row when the supplied row_key is not present in the table's internal _row_locations mapping. Textual uses stable RowKey objects (or their string ids) to track rows independently of their visual position, so this error means no row with that key has ever been added (or it was removed/cleared). It is the canonical 'unknown row key' error for key-based row access.

Source

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

            )
        row_index = self._row_locations.get(row_key)
        column_index = self._column_locations.get(column_key)
        return Coordinate(row_index, column_index)

    def get_row(self, row_key: RowKey | str) -> list[CellType]:
        """Get the values from the row identified by the given row key.

        Args:
            row_key: The key of the row.

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

        Raises:
            RowDoesNotExist: When there is no row corresponding to the key.
        """
        if row_key not in self._row_locations:
            raise RowDoesNotExist(f"Row key {row_key!r} is not valid.")
        cell_mapping: dict[ColumnKey, CellType] = self._data.get(row_key, {})
        ordered_row: list[CellType] = [
            cell_mapping[column.key] for column in self.ordered_columns
        ]
        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:

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Verify the key exists first: if row_key in table.row_keys or catch RowDoesNotExist.
  2. Re-fetch keys from the table after clear()/re-population instead of caching them across rebuilds.
  3. Ensure you are passing the RowKey returned by add_row(), not an index or a ColumnKey.
  4. If rows were removed, guard with table.get_row_index(row_key) inside try/except RowDoesNotExist before calling get_row.

Example fix

// before
row = table.get_row(stale_key)

// after
from textual.widgets._data_table import RowDoesNotExist
try:
    row = table.get_row(stale_key)
except RowDoesNotExist:
    row = None
Defensive patterns

Strategy: try-catch

Validate before calling

key_exists = row_key in table.row_keys
row = table.get_row(row_key) if key_exists else None

Type guard

from textual.widgets._data_table import RowKey

def is_live_row_key(table: DataTable, key: object) -> bool:
    return isinstance(key, (RowKey, str)) and key in table._row_locations

Try / catch

from textual.widgets._data_table import RowDoesNotExist
try:
    row = table.get_row(row_key)
except RowDoesNotExist:
    row = None  # treat as missing

Prevention

When it happens

Trigger: Calling table.get_row(key) with a key that was never returned by add_row/add_columns+add_row, a key from a table that had clear() called on it, a key from a different DataTable instance, or a key of a row removed via remove_row.

Common situations: Reusing stale row keys captured before a table refresh/clear; passing a ColumnKey where a RowKey is expected; rebuilding table data asynchronously while a callback still holds old keys; typos in string keys supplied to add_row(key=...).

Related errors


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