Exception on duplicate row and column keys

This commit is contained in:
Darren Burns
2023-02-13 14:09:33 +00:00
parent 196054f6b0
commit 12a58f838f
3 changed files with 38 additions and 7 deletions

View File

@@ -45,6 +45,11 @@ class CellDoesNotExist(Exception):
do not exist in the DataTable.""" do not exist in the DataTable."""
class DuplicateKey(Exception):
"""Raised when the RowKey or ColumnKey provided already refers to
an existing row or column in the DataTable. Keys must be unique."""
@functools.total_ordering @functools.total_ordering
class StringKey: class StringKey:
"""An object used as a key in a mapping. It can optionally wrap a string, """An object used as a key in a mapping. It can optionally wrap a string,
@@ -456,7 +461,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
"""Cached y_offset - key is update_count - see y_offsets property for more """Cached y_offset - key is update_count - see y_offsets property for more
information """ information """
self._ordered_row_cache: LRUCache[tuple[int, int], list[Row]] = LRUCache(1) self._ordered_row_cache: LRUCache[tuple[int, int], list[Row]] = LRUCache(1)
"""Caches row ordering - key is update_count.""" """Caches row ordering - key is (num_rows, update_count)."""
self._require_update_dimensions: bool = False self._require_update_dimensions: bool = False
"""Set to re-calculate dimensions on idle.""" """Set to re-calculate dimensions on idle."""
@@ -917,6 +922,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
after being added due to sorting/insertion/deletion of other columns). after being added due to sorting/insertion/deletion of other columns).
""" """
column_key = ColumnKey(key) column_key = ColumnKey(key)
if column_key in self._column_locations:
raise DuplicateKey(f"The column key {key!r} already exists.")
column_index = len(self.columns) column_index = len(self.columns)
label = Text.from_markup(label) if isinstance(label, str) else label label = Text.from_markup(label) if isinstance(label, str) else label
content_width = measure(self.app.console, label, 1) content_width = measure(self.app.console, label, 1)
@@ -959,13 +966,15 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
of its current location in the DataTable (it could have moved after of its current location in the DataTable (it could have moved after
being added due to sorting or insertion/deletion of other rows). being added due to sorting or insertion/deletion of other rows).
""" """
row_index = self.row_count
row_key = RowKey(key) row_key = RowKey(key)
if row_key in self._row_locations:
raise DuplicateKey(f"The row key {row_key!r} already exists.")
# TODO: If there are no columns: do we generate them here? # TODO: If there are no columns: do we generate them here?
# If we don't do this, users will be required to call add_column(s) # If we don't do this, users will be required to call add_column(s)
# Before they call add_row. # Before they call add_row.
row_index = self.row_count
# Map the key of this row to its current index # Map the key of this row to its current index
self._row_locations[row_key] = row_index self._row_locations[row_key] = row_index
self.data[row_key] = { self.data[row_key] = {

View File

@@ -1,14 +1,15 @@
"""Make non-widget DataTable support classes available.""" """Make non-widget DataTable support classes available."""
from ._data_table import ( from ._data_table import (
CellDoesNotExist,
CellKey,
CellType,
Column, Column,
ColumnKey,
CursorType,
DuplicateKey,
Row, Row,
RowKey, RowKey,
ColumnKey,
CellKey,
CursorType,
CellType,
CellDoesNotExist,
) )
__all__ = [ __all__ = [
@@ -20,4 +21,5 @@ __all__ = [
"CursorType", "CursorType",
"CellType", "CellType",
"CellDoesNotExist", "CellDoesNotExist",
"DuplicateKey",
] ]

View File

@@ -11,6 +11,7 @@ from textual.events import Click, MouseMove
from textual.message import Message from textual.message import Message
from textual.message_pump import MessagePump from textual.message_pump import MessagePump
from textual.widgets import DataTable from textual.widgets import DataTable
from textual.widgets._data_table import DuplicateKey
from textual.widgets.data_table import CellDoesNotExist, CellKey, ColumnKey, Row, RowKey from textual.widgets.data_table import CellDoesNotExist, CellKey, ColumnKey, Row, RowKey
ROWS = [["0/0", "0/1"], ["1/0", "1/1"], ["2/0", "2/1"]] ROWS = [["0/0", "0/1"], ["1/0", "1/1"], ["2/0", "2/1"]]
@@ -192,6 +193,25 @@ async def test_add_rows_user_defined_keys():
assert table.rows["algernon"] == first_row assert table.rows["algernon"] == first_row
async def test_add_row_duplicate_key():
app = DataTableApp()
async with app.run_test():
table = app.query_one(DataTable)
table.add_column("A")
table.add_row("1", key="1")
with pytest.raises(DuplicateKey):
table.add_row("2", key="1") # Duplicate row key
async def test_add_column_duplicate_key():
app = DataTableApp()
async with app.run_test():
table = app.query_one(DataTable)
table.add_column("A", key="A")
with pytest.raises(DuplicateKey):
table.add_column("B", key="A") # Duplicate column key
async def test_add_column_with_width(): async def test_add_column_with_width():
app = DataTableApp() app = DataTableApp()
async with app.run_test(): async with app.run_test():