{"record":{"id":"4c5e21cec85abb94","repo":"Textualize/textual","slug":"the-row-key-row-key-r-already-exists","errorCode":null,"errorMessage":"The row key {row_key!r} already exists.","messagePattern":"The row key (.+?) already exists\\.","errorType":"exception","errorClass":"DuplicateKey","httpStatus":null,"severity":"error","filePath":"src/textual/widgets/_data_table.py","lineNumber":1693,"sourceCode":"    ) -> RowKey:\n        \"\"\"Add a row at the bottom of the DataTable.\n\n        Args:\n            *cells: Positional arguments should contain cell data.\n            height: The height of a row (in lines). Use `None` to auto-detect the optimal\n                height.\n            key: A key which uniquely identifies this row. If None, it will be generated\n                for you and returned.\n            label: The label for the row. Will be displayed to the left if supplied.\n\n        Returns:\n            Unique identifier for this row. Can be used to retrieve this row regardless\n                of its current location in the DataTable (it could have moved after\n                being added due to sorting or insertion/deletion of other rows).\n        \"\"\"\n        row_key = RowKey(key)\n        if row_key in self._row_locations:\n            raise DuplicateKey(f\"The row key {row_key!r} already exists.\")\n\n        # TODO: If there are no columns: do we generate them here?\n        #  If we don't do this, users will be required to call add_column(s)\n        #  Before they call add_row.\n\n        if len(cells) > len(self.ordered_columns):\n            raise ValueError(\"More values provided than there are columns.\")\n\n        row_index = self.row_count\n        # Map the key of this row to its current index\n        self._row_locations[row_key] = row_index\n        self._data[row_key] = {\n            column.key: cell\n            for column, cell in zip_longest(self.ordered_columns, cells)\n        }\n\n        label = Text.from_markup(label, end=\"\") if isinstance(label, str) else label\n","sourceCodeStart":1675,"sourceCodeEnd":1711,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/widgets/_data_table.py#L1675-L1711","documentation":"Raised by DataTable.add_row when row_key already exists in _row_locations. Row keys must be unique; this commonly fires when a caller supplies explicit keys and re-adds a row that already exists (e.g. during data refresh), because add_row does not upsert.","triggerScenarios":"Calling add_row(..., key=k) twice with the same key; periodic refresh jobs that append rows with stable IDs without deduplicating; re-adding rows after clear() of columns only.","commonSituations":"Polling loops appending log/feed rows keyed by message id; reloading a dataset without clearing; retries of failed add_row calls that actually succeeded.","solutions":["Deduplicate before adding: if key not in table.row_keys: table.add_row(..., key=key).","For refreshes, remove existing rows (remove_row) or clear() before re-adding.","Let Textual auto-generate keys (omit key=) when uniqueness tracking is not needed.","Catch DuplicateKey to make refresh logic idempotent."],"exampleFix":"# before\ntable.add_row(*values, key=item_id)\n\n# after\nif item_id not in table.row_keys:\n    table.add_row(*values, key=item_id)","handlingStrategy":"validation","validationCode":"if item_id not in table.row_keys:\n    table.add_row(*values, key=item_id)","typeGuard":null,"tryCatchPattern":"from textual.widgets._data_table import DuplicateKey\ntry:\n    table.add_row(*values, key=item_id)\nexcept DuplicateKey:\n    pass  # row already present, idempotent refresh","preventionTips":["Deduplicate by key before add_row in refresh loops","Remove or clear rows before re-adding","Omit key= to let Textual generate unique keys when tracking isn't needed"],"tags":["data-table","duplicate-key","textual","row"],"backgroundTag":"duplicate-key-insert","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}