From ab9cc5922a47ecf4b209ea0d8fd34cdd9eb470a0 Mon Sep 17 00:00:00 2001 From: Louis Deconinck Date: Thu, 10 Sep 2026 20:46:56 +0200 Subject: [PATCH] Fix selector state cleanup after callback errors --- lib/matplotlib/tests/test_widgets.py | 14 ++++++++++++++ lib/matplotlib/widgets.py | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 1aa6b70e7790..16f078d8c487 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -681,6 +681,20 @@ def test_span_selector_onselect(ax, interactive): onselect.assert_called_once() +def test_selector_state_is_reset_when_callback_raises(ax): + onselect = mock.Mock(side_effect=[RuntimeError, None]) + tool = widgets.SpanSelector(ax, onselect, 'horizontal') + + with pytest.raises(RuntimeError): + click_and_drag(tool, start=(100, 100), end=(150, 100)) + + assert tool._eventpress is None + assert tool._eventrelease is None + + click_and_drag(tool, start=(100, 100), end=(150, 100)) + assert tool._selection_completed + + @pytest.mark.parametrize('ignore_event_outside', [True, False]) def test_span_selector_ignore_outside(ax, ignore_event_outside): onselect = mock.Mock(spec=noop, return_value=None) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index daec97f42ce6..20d9e48a8723 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2502,10 +2502,12 @@ def release(self, event): if not self.ignore(event) and self._eventpress: event = self._clean_event(event) self._eventrelease = event - self._release(event) - self._eventpress = None - self._eventrelease = None - self._state.discard('move') + try: + self._release(event) + finally: + self._eventpress = None + self._eventrelease = None + self._state.discard('move') return True return False