Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 568a039

Browse filesBrowse files
authored
Merge pull request #28507 from meeseeksmachine/auto-backport-of-pr-28430-on-v3.9.x
Backport PR #28430 on branch v3.9.x (Fix pickling of AxesWidgets.)
2 parents 7fa9f24 + 06189c2 commit 568a039
Copy full SHA for 568a039

File tree

6 files changed

+67
-54
lines changed
Filter options

6 files changed

+67
-54
lines changed

‎lib/matplotlib/testing/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/testing/__init__.py
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,24 @@ def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backe
211211
)
212212

213213
assert proc.stdout.strip().endswith(f"'{expected_backend}'")
214+
215+
216+
def is_ci_environment():
217+
# Common CI variables
218+
ci_environment_variables = [
219+
'CI', # Generic CI environment variable
220+
'CONTINUOUS_INTEGRATION', # Generic CI environment variable
221+
'TRAVIS', # Travis CI
222+
'CIRCLECI', # CircleCI
223+
'JENKINS', # Jenkins
224+
'GITLAB_CI', # GitLab CI
225+
'GITHUB_ACTIONS', # GitHub Actions
226+
'TEAMCITY_VERSION' # TeamCity
227+
# Add other CI environment variables as needed
228+
]
229+
230+
for env_var in ci_environment_variables:
231+
if os.getenv(env_var):
232+
return True
233+
234+
return False

‎lib/matplotlib/testing/__init__.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/testing/__init__.pyi
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ def ipython_in_subprocess(
5151
requested_backend_or_gui_framework: str,
5252
all_expected_backends: dict[tuple[int, int], str],
5353
) -> None: ...
54+
def is_ci_environment() -> bool: ...

‎lib/matplotlib/tests/test_backends_interactive.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backends_interactive.py
+1-22Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import matplotlib as mpl
2020
from matplotlib import _c_internal_utils
2121
from matplotlib.backend_tools import ToolToggleBase
22-
from matplotlib.testing import subprocess_run_helper as _run_helper
22+
from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment
2323

2424

2525
class _WaitForStringPopen(subprocess.Popen):
@@ -110,27 +110,6 @@ def _get_testable_interactive_backends():
110110
for env, marks in _get_available_interactive_backends()]
111111

112112

113-
def is_ci_environment():
114-
# Common CI variables
115-
ci_environment_variables = [
116-
'CI', # Generic CI environment variable
117-
'CONTINUOUS_INTEGRATION', # Generic CI environment variable
118-
'TRAVIS', # Travis CI
119-
'CIRCLECI', # CircleCI
120-
'JENKINS', # Jenkins
121-
'GITLAB_CI', # GitLab CI
122-
'GITHUB_ACTIONS', # GitHub Actions
123-
'TEAMCITY_VERSION' # TeamCity
124-
# Add other CI environment variables as needed
125-
]
126-
127-
for env_var in ci_environment_variables:
128-
if os.getenv(env_var):
129-
return True
130-
131-
return False
132-
133-
134113
# Reasonable safe values for slower CI/Remote and local architectures.
135114
_test_timeout = 120 if is_ci_environment() else 20
136115

‎lib/matplotlib/tests/test_pickle.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_pickle.py
+23-1Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from io import BytesIO
22
import ast
3+
import os
4+
import sys
35
import pickle
46
import pickletools
57

@@ -8,7 +10,7 @@
810

911
import matplotlib as mpl
1012
from matplotlib import cm
11-
from matplotlib.testing import subprocess_run_helper
13+
from matplotlib.testing import subprocess_run_helper, is_ci_environment
1214
from matplotlib.testing.decorators import check_figures_equal
1315
from matplotlib.dates import rrulewrapper
1416
from matplotlib.lines import VertexSelector
@@ -307,3 +309,23 @@ def test_cycler():
307309
ax = pickle.loads(pickle.dumps(ax))
308310
l, = ax.plot([3, 4])
309311
assert l.get_color() == "m"
312+
313+
314+
# Run under an interactive backend to test that we don't try to pickle the
315+
# (interactive and non-picklable) canvas.
316+
def _test_axeswidget_interactive():
317+
ax = plt.figure().add_subplot()
318+
pickle.dumps(mpl.widgets.Button(ax, "button"))
319+
320+
321+
@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649
322+
('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
323+
sys.platform == 'darwin' and sys.version_info[:2] < (3, 11),
324+
reason='Tk version mismatch on Azure macOS CI'
325+
)
326+
def test_axeswidget_interactive():
327+
subprocess_run_helper(
328+
_test_axeswidget_interactive,
329+
timeout=120 if is_ci_environment() else 20,
330+
extra_env={'MPLBACKEND': 'tkagg'}
331+
)

‎lib/matplotlib/widgets.py

Copy file name to clipboardExpand all lines: lib/matplotlib/widgets.py
+12-28Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,6 @@ def ignore(self, event):
9090
"""
9191
return not self.active
9292

93-
def _changed_canvas(self):
94-
"""
95-
Someone has switched the canvas on us!
96-
97-
This happens if `savefig` needs to save to a format the previous
98-
backend did not support (e.g. saving a figure using an Agg based
99-
backend saved to a vector format).
100-
101-
Returns
102-
-------
103-
bool
104-
True if the canvas has been changed.
105-
106-
"""
107-
return self.canvas is not self.ax.figure.canvas
108-
10993

11094
class AxesWidget(Widget):
11195
"""
@@ -131,9 +115,10 @@ class AxesWidget(Widget):
131115

132116
def __init__(self, ax):
133117
self.ax = ax
134-
self.canvas = ax.figure.canvas
135118
self._cids = []
136119

120+
canvas = property(lambda self: self.ax.figure.canvas)
121+
137122
def connect_event(self, event, callback):
138123
"""
139124
Connect a callback function with an event.
@@ -1100,7 +1085,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
11001085

11011086
def _clear(self, event):
11021087
"""Internal event handler to clear the buttons."""
1103-
if self.ignore(event) or self._changed_canvas():
1088+
if self.ignore(event) or self.canvas.is_saving():
11041089
return
11051090
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
11061091
self.ax.draw_artist(self._checks)
@@ -1677,7 +1662,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16771662

16781663
def _clear(self, event):
16791664
"""Internal event handler to clear the buttons."""
1680-
if self.ignore(event) or self._changed_canvas():
1665+
if self.ignore(event) or self.canvas.is_saving():
16811666
return
16821667
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
16831668
self.ax.draw_artist(self._buttons)
@@ -1933,7 +1918,7 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False,
19331918

19341919
def clear(self, event):
19351920
"""Internal event handler to clear the cursor."""
1936-
if self.ignore(event) or self._changed_canvas():
1921+
if self.ignore(event) or self.canvas.is_saving():
19371922
return
19381923
if self.useblit:
19391924
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
@@ -2573,9 +2558,7 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,
25732558
self.drag_from_anywhere = drag_from_anywhere
25742559
self.ignore_event_outside = ignore_event_outside
25752560

2576-
# Reset canvas so that `new_axes` connects events.
2577-
self.canvas = None
2578-
self.new_axes(ax, _props=props)
2561+
self.new_axes(ax, _props=props, _init=True)
25792562

25802563
# Setup handles
25812564
self._handle_props = {
@@ -2588,14 +2571,15 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,
25882571

25892572
self._active_handle = None
25902573

2591-
def new_axes(self, ax, *, _props=None):
2574+
def new_axes(self, ax, *, _props=None, _init=False):
25922575
"""Set SpanSelector to operate on a new Axes."""
2593-
self.ax = ax
2594-
if self.canvas is not ax.figure.canvas:
2576+
reconnect = False
2577+
if _init or self.canvas is not ax.figure.canvas:
25952578
if self.canvas is not None:
25962579
self.disconnect_events()
2597-
2598-
self.canvas = ax.figure.canvas
2580+
reconnect = True
2581+
self.ax = ax
2582+
if reconnect:
25992583
self.connect_default_events()
26002584

26012585
# Reset

‎lib/matplotlib/widgets.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/widgets.pyi
+9-3Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ class Widget:
3333

3434
class AxesWidget(Widget):
3535
ax: Axes
36-
canvas: FigureCanvasBase | None
3736
def __init__(self, ax: Axes) -> None: ...
37+
@property
38+
def canvas(self) -> FigureCanvasBase | None: ...
3839
def connect_event(self, event: Event, callback: Callable) -> None: ...
3940
def disconnect_events(self) -> None: ...
4041

@@ -310,7 +311,6 @@ class SpanSelector(_SelectorWidget):
310311
grab_range: float
311312
drag_from_anywhere: bool
312313
ignore_event_outside: bool
313-
canvas: FigureCanvasBase | None
314314
def __init__(
315315
self,
316316
ax: Axes,
@@ -330,7 +330,13 @@ class SpanSelector(_SelectorWidget):
330330
ignore_event_outside: bool = ...,
331331
snap_values: ArrayLike | None = ...,
332332
) -> None: ...
333-
def new_axes(self, ax: Axes, *, _props: dict[str, Any] | None = ...) -> None: ...
333+
def new_axes(
334+
self,
335+
ax: Axes,
336+
*,
337+
_props: dict[str, Any] | None = ...,
338+
_init: bool = ...,
339+
) -> None: ...
334340
def connect_default_events(self) -> None: ...
335341
@property
336342
def direction(self) -> Literal["horizontal", "vertical"]: ...

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.