From de183420ad075c732a2314771f976cef1e609422 Mon Sep 17 00:00:00 2001 From: Andrew Glick <17516195+Antyos@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:33:38 -0500 Subject: [PATCH 1/6] feat: Add _normalize_cell_index() to Qt Table backend --- src/magicgui/backends/_qtpy/widgets.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/magicgui/backends/_qtpy/widgets.py b/src/magicgui/backends/_qtpy/widgets.py index 939ed3a8..f9b32c04 100644 --- a/src/magicgui/backends/_qtpy/widgets.py +++ b/src/magicgui/backends/_qtpy/widgets.py @@ -1512,8 +1512,26 @@ def _mgui_remove_row(self, row: int) -> None: def _mgui_remove_column(self, column: int) -> None: self._qwidget.removeColumn(column) + def _normalize_cell_index(self, row: int, col: int) -> tuple[int, int]: + num_rows = self._mgui_get_row_count() + num_cols = self._mgui_get_column_count() + # Offset negative indices because Qt doesn't natively support them + if row < 0: + normalized_row = row + num_rows + else: + normalized_row = row + if col < 0: + normalized_col = col + num_cols + else: + normalized_col = col + # Qt will not raise an error for attempting to index into an invalid column, but normal Pyhton does. + if normalized_row >= num_rows or normalized_col >= num_cols: + raise IndexError(f"Index ({row}, {col}) is out of bounds for table of shape ({num_rows}, {num_cols}).") + return normalized_row, normalized_col + def _mgui_get_cell(self, row: int, col: int) -> Any: """Get current value of the widget.""" + row, col = self._normalize_cell_index(row, col) item = self._qwidget.item(row, col) if item: return item.data(_DATA_ROLE) @@ -1523,6 +1541,7 @@ def _mgui_get_cell(self, row: int, col: int) -> Any: def _mgui_set_cell(self, row: int, col: int, value: Any) -> None: """Set current value of the widget.""" + row, col = self._normalize_cell_index(row, col) if value is None: self._qwidget.setItem(row, col, None) self._qwidget.removeCellWidget(row, col) From 6fedbf9cdc118917dc79bb2b2c17221fee90cb0d Mon Sep 17 00:00:00 2001 From: Andrew Glick <17516195+Antyos@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:55:58 -0500 Subject: [PATCH 2/6] fix: Check for too-negative invalid indices --- src/magicgui/backends/_qtpy/widgets.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/magicgui/backends/_qtpy/widgets.py b/src/magicgui/backends/_qtpy/widgets.py index f9b32c04..50350098 100644 --- a/src/magicgui/backends/_qtpy/widgets.py +++ b/src/magicgui/backends/_qtpy/widgets.py @@ -1520,13 +1520,20 @@ def _normalize_cell_index(self, row: int, col: int) -> tuple[int, int]: normalized_row = row + num_rows else: normalized_row = row - if col < 0: + if col < 0: normalized_col = col + num_cols else: normalized_col = col - # Qt will not raise an error for attempting to index into an invalid column, but normal Pyhton does. - if normalized_row >= num_rows or normalized_col >= num_cols: - raise IndexError(f"Index ({row}, {col}) is out of bounds for table of shape ({num_rows}, {num_cols}).") + # Qt will not raise an error for attempting to index into an invalid cell, but normal Python does. + if ( + normalized_row < 0 + or normalized_col < 0 + or normalized_row >= num_rows + or normalized_col >= num_cols + ): + raise IndexError( + f"Index ({row}, {col}) is out of bounds for table of shape ({num_rows}, {num_cols})." + ) return normalized_row, normalized_col def _mgui_get_cell(self, row: int, col: int) -> Any: From 466fa95b986e650ed046ace2b52db3f77d12a909 Mon Sep 17 00:00:00 2001 From: Andrew Glick <17516195+Antyos@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:56:26 -0500 Subject: [PATCH 3/6] fix: Iterable in pytest fixture is deprecated --- tests/test_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_table.py b/tests/test_table.py index 6a9ef2cb..9c4b7929 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -200,7 +200,7 @@ def test_dataview_getitem(index): assert np.allclose(table.data[index], data[index]) -@pytest.mark.parametrize("index, value", zip(INDICES, VALUES)) +@pytest.mark.parametrize("index, value", tuple(zip(INDICES, VALUES))) def test_dataview_setitem(index, value): """Test that table.data can be indexed like a numpy array.""" np = pytest.importorskip("numpy") From 19eaf62fc3e8395504fa5809a097abc5de5df7c6 Mon Sep 17 00:00:00 2001 From: Andrew Glick <17516195+Antyos@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:56:51 -0500 Subject: [PATCH 4/6] test: Check negative and invalid indices in table data view --- tests/test_table.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_table.py b/tests/test_table.py index 9c4b7929..1fc9cb76 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -171,6 +171,8 @@ def test_table_from_numpy(): INDICES = ( 1, (2, 2), + -2, + (-1, -1), (slice(None), 2), (slice(None), slice(None)), (slice(1, 3), slice(3)), @@ -181,6 +183,8 @@ def test_table_from_numpy(): VALUES = ( (7,) * 4, 6, + (14,) * 4, + 25, (7,) * 6, [[1] * 4] * 6, [[1] * 3] * 2, @@ -212,6 +216,34 @@ def test_dataview_setitem(index, value): data[index] = value assert np.allclose(table.data.to_list(), data) +INVLAID_INDICES = ( + (6, 0), + (0, 4), + (-7, 0), + (0, -5) +) + +@pytest.mark.parametrize("index", INVLAID_INDICES) +def test_dataview_getitem_invalid_index(index): + """Test that invalid cell indices raise IndexError.""" + np = pytest.importorskip("numpy") + data = np.arange(24).reshape(6, 4) + + table = Table(value=data) + with pytest.raises(IndexError): + table.data[index] + + +@pytest.mark.parametrize("index", INVLAID_INDICES) +def test_dataview_setitem_invalid_index(index): + """Test that invalid cell indices raise IndexError.""" + np = pytest.importorskip("numpy") + data = np.arange(24).reshape(6, 4) + + table = Table(value=data) + with pytest.raises(IndexError): + table.data[index] = 999 + def test_dataview_delitem(): """Test that table.data can be indexed like a numpy array.""" From dea9ed67147c67c9debd9e1ed55715cdb7178eea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:01:41 +0000 Subject: [PATCH 5/6] style(pre-commit.ci): auto fixes [...] --- tests/test_table.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_table.py b/tests/test_table.py index 1fc9cb76..59e45082 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -216,12 +216,9 @@ def test_dataview_setitem(index, value): data[index] = value assert np.allclose(table.data.to_list(), data) -INVLAID_INDICES = ( - (6, 0), - (0, 4), - (-7, 0), - (0, -5) -) + +INVLAID_INDICES = ((6, 0), (0, 4), (-7, 0), (0, -5)) + @pytest.mark.parametrize("index", INVLAID_INDICES) def test_dataview_getitem_invalid_index(index): From 17bd6044cf99eb3ba0d3f55f4e9f67fb99ca936f Mon Sep 17 00:00:00 2001 From: Andrew Glick <17516195+Antyos@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:07:35 -0500 Subject: [PATCH 6/6] chore: Typos and formatting --- src/magicgui/backends/_qtpy/widgets.py | 6 ++++-- tests/test_table.py | 13 +++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/magicgui/backends/_qtpy/widgets.py b/src/magicgui/backends/_qtpy/widgets.py index 50350098..f6979a8d 100644 --- a/src/magicgui/backends/_qtpy/widgets.py +++ b/src/magicgui/backends/_qtpy/widgets.py @@ -1524,7 +1524,8 @@ def _normalize_cell_index(self, row: int, col: int) -> tuple[int, int]: normalized_col = col + num_cols else: normalized_col = col - # Qt will not raise an error for attempting to index into an invalid cell, but normal Python does. + # Qt will not raise an error for attempting to index into an invalid cell, but + # normal Python does. if ( normalized_row < 0 or normalized_col < 0 @@ -1532,7 +1533,8 @@ def _normalize_cell_index(self, row: int, col: int) -> tuple[int, int]: or normalized_col >= num_cols ): raise IndexError( - f"Index ({row}, {col}) is out of bounds for table of shape ({num_rows}, {num_cols})." + f"Index ({row}, {col}) is out of bounds for table of shape" + f" ({num_rows}, {num_cols})." ) return normalized_row, normalized_col diff --git a/tests/test_table.py b/tests/test_table.py index 1fc9cb76..5d2c2d19 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -216,14 +216,11 @@ def test_dataview_setitem(index, value): data[index] = value assert np.allclose(table.data.to_list(), data) -INVLAID_INDICES = ( - (6, 0), - (0, 4), - (-7, 0), - (0, -5) -) -@pytest.mark.parametrize("index", INVLAID_INDICES) +INVALID_INDICES = ((6, 0), (0, 4), (-7, 0), (0, -5)) + + +@pytest.mark.parametrize("index", INVALID_INDICES) def test_dataview_getitem_invalid_index(index): """Test that invalid cell indices raise IndexError.""" np = pytest.importorskip("numpy") @@ -234,7 +231,7 @@ def test_dataview_getitem_invalid_index(index): table.data[index] -@pytest.mark.parametrize("index", INVLAID_INDICES) +@pytest.mark.parametrize("index", INVALID_INDICES) def test_dataview_setitem_invalid_index(index): """Test that invalid cell indices raise IndexError.""" np = pytest.importorskip("numpy")