diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json
index 3912afd8a17d..36e743db21b8 100644
--- a/doc/_static/switcher.json
+++ b/doc/_static/switcher.json
@@ -1,7 +1,7 @@
[
{
"name": "3.10 (stable)",
- "version": "3.10.7",
+ "version": "3.10.8",
"url": "https://matplotlib.org/stable/",
"preferred": true
},
diff --git a/doc/_static/zenodo_cache/17595503.svg b/doc/_static/zenodo_cache/17595503.svg
new file mode 100644
index 000000000000..891bd118d125
--- /dev/null
+++ b/doc/_static/zenodo_cache/17595503.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/api/prev_api_changes/api_changes_3.10.7.rst b/doc/api/prev_api_changes/api_changes_3.10.7.rst
index 16b00597bbfb..a60061e86277 100644
--- a/doc/api/prev_api_changes/api_changes_3.10.7.rst
+++ b/doc/api/prev_api_changes/api_changes_3.10.7.rst
@@ -1,4 +1,4 @@
-API Changes for 3.10.1
+API Changes for 3.10.7
======================
Development
diff --git a/doc/conf.py b/doc/conf.py
index 96738492b68b..292aff3e1983 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -200,8 +200,6 @@ def _check_dependencies():
else:
sg_matplotlib_animations = True
-# The following import is only necessary to monkey patch the signature later on
-from sphinx_gallery import gen_rst
# Prevent plt.show() from emitting a non-GUI backend warning.
warnings.filterwarnings('ignore', category=UserWarning,
@@ -299,7 +297,7 @@ def autodoc_process_bases(app, name, obj, options, bases):
'reference_url': {'matplotlib': None, 'mpl_toolkits': None},
'prefer_full_module': {r'mpl_toolkits\.'},
'remove_config_comments': True,
- 'reset_modules': ('matplotlib', clear_basic_units),
+ 'reset_modules': ('matplotlib', clear_basic_units, 'sphinxext.util.patch_header'),
'subsection_order': gallery_order_sectionorder,
'thumbnail_size': (320, 224),
'within_subsection_order': gallery_order_subsectionorder,
@@ -347,31 +345,6 @@ def gallery_image_warning_filter(record):
mathmpl_fontsize = 11.0
mathmpl_srcset = ['2x']
-# Monkey-patching gallery header to include search keywords
-gen_rst.EXAMPLE_HEADER = """
-.. DO NOT EDIT.
-.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
-.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
-.. "{0}"
-.. LINE NUMBERS ARE GIVEN BELOW.
-
-.. only:: html
-
- .. meta::
- :keywords: codex
-
- .. note::
- :class: sphx-glr-download-link-note
-
- :ref:`Go to the end `
- to download the full example code.{2}
-
-.. rst-class:: sphx-glr-example-title
-
-.. _sphx_glr_{1}:
-
-"""
-
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst
index 95d170a06b00..4a2ace7f9883 100644
--- a/doc/install/dependencies.rst
+++ b/doc/install/dependencies.rst
@@ -220,7 +220,7 @@ Build dependencies
Python
------
-``pip`` normally builds packages using :external+pip:doc:`build isolation `,
+``pip`` normally builds packages using :external+pip:doc:`build isolation `,
which means that ``pip`` installs the dependencies listed here for the
duration of the build process. However, build isolation is disabled via the the
:external+pip:ref:`--no-build-isolation ` flag
diff --git a/doc/project/citing.rst b/doc/project/citing.rst
index c5e56e6f12d4..1f8c6509e9e6 100644
--- a/doc/project/citing.rst
+++ b/doc/project/citing.rst
@@ -32,6 +32,9 @@ By version
.. START OF AUTOGENERATED
+v3.10.8
+ .. image:: ../_static/zenodo_cache/17595503.svg
+ :target: https://doi.org/10.5281/zenodo.17595503
v3.10.7
.. image:: ../_static/zenodo_cache/17298696.svg
:target: https://doi.org/10.5281/zenodo.17298696
diff --git a/doc/sphinxext/util.py b/doc/sphinxext/util.py
index 14097ba9396a..c0f336eaea18 100644
--- a/doc/sphinxext/util.py
+++ b/doc/sphinxext/util.py
@@ -1,5 +1,7 @@
import sys
+from sphinx_gallery import gen_rst
+
def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf,
**kwargs):
@@ -19,3 +21,33 @@ def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf,
# Clear basic_units module to re-register with unit registry on import.
def clear_basic_units(gallery_conf, fname):
return sys.modules.pop('basic_units', None)
+
+
+# Monkey-patching gallery header to include search keywords
+EXAMPLE_HEADER = """
+.. DO NOT EDIT.
+.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
+.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
+.. "{0}"
+.. LINE NUMBERS ARE GIVEN BELOW.
+
+.. only:: html
+
+ .. meta::
+ :keywords: codex
+
+ .. note::
+ :class: sphx-glr-download-link-note
+
+ :ref:`Go to the end `
+ to download the full example code{2}
+
+.. rst-class:: sphx-glr-example-title
+
+.. _sphx_glr_{1}:
+
+"""
+
+
+def patch_header(gallery_conf, fname):
+ gen_rst.EXAMPLE_HEADER = EXAMPLE_HEADER
diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst
index c40bedb4cb5b..8d93759ac756 100644
--- a/doc/users/github_stats.rst
+++ b/doc/users/github_stats.rst
@@ -1,19 +1,18 @@
.. _github-stats:
-GitHub statistics for 3.10.7 (Oct 08, 2025)
+GitHub statistics for 3.10.8 (Nov 12, 2025)
===========================================
-GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/10/08
+GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/11/12
These lists are automatically generated, and may be incomplete or contain duplicates.
We closed 4 issues and merged 16 pull requests.
-The full list can be seen `on GitHub `__
+The full list can be seen `on GitHub `__
-The following 32 authors contributed 422 commits.
+The following 35 authors contributed 445 commits.
* Aasma Gupta
-* AASMA GUPTA
* Antony Lee
* Christine P. Chai
* David Stansby
@@ -22,6 +21,7 @@ The following 32 authors contributed 422 commits.
* G.D. McBain
* Greg Lucas
* hannah
+* heinrich5991
* hu-xiaonan
* Ian Thomas
* Inês Cachola
@@ -29,19 +29,22 @@ The following 32 authors contributed 422 commits.
* Jouni K. Seppänen
* Khushi_29
* Kyle Sunden
+* Lucas Gruwez
* Lumberbot (aka Jack)
* N R Navaneet
* Nathan G. Wiseman
+* Nathan Goldbaum
+* Nick Coish
* Oscar Gustafsson
* Praful Gulani
* Qian Zhang
+* Rafael Katri
* Raphael Erik Hviding
* Roman
* Ruth Comer
* saikarna913
* Scott Shambaugh
* Thomas A Caswell
-* Tim Heap
* Tim Hoffmann
* Trygve Magnus Ræder
@@ -49,29 +52,29 @@ GitHub issues and pull requests:
Pull Requests (16):
-* :ghpull:`30628`: Backport PR #30626 on branch v3.10.x (MNT: Fix new F401 unused imports warnings)
-* :ghpull:`30626`: MNT: Fix new F401 unused imports warnings
-* :ghpull:`30589`: Backport PR #29745: Use PEP8 style method and function names from
-* :ghpull:`30614`: Backport PR #30612 on branch v3.10.x (MNT: update black pin)
-* :ghpull:`30612`: MNT: update black pin
-* :ghpull:`30572`: Backport PR #30571 on branch v3.10.x (CI: remove macos13)
-* :ghpull:`30571`: CI: remove macos13
-* :ghpull:`30570`: Backport PR #30558 on branch v3.10.x (Fix stubtest with mypy 18)
-* :ghpull:`30558`: Fix stubtest with mypy 18
-* :ghpull:`30540`: Backport PR #30539 on branch v3.10.x (Fix scale_unit/scale_units typo in quiver docs)
-* :ghpull:`30539`: Fix scale_unit/scale_units typo in quiver docs
-* :ghpull:`30518`: Backport PR #30497 on branch v3.10.x (TST: Use a temporary directory for test_save_figure_return)
-* :ghpull:`30497`: TST: Use a temporary directory for test_save_figure_return
-* :ghpull:`30506`: Backport PR #30490 on branch v3.10.x (Fix SVG rendering error in def update_background)
-* :ghpull:`30490`: Fix SVG rendering error in def update_background
-* :ghpull:`30494`: Backport PR #30492 on branch v3.10.x (DOC: pytz link should be from PyPI)
+* :ghpull:`30717`: Backport PR #30714 on branch v3.10.x (FIX: Gracefully handle numpy arrays as input to check_in_list())
+* :ghpull:`30714`: FIX: Gracefully handle numpy arrays as input to check_in_list()
+* :ghpull:`30560`: Consistent zoom boxes
+* :ghpull:`30711`: Backport PR #30697 on branch v3.10.x (BUG: raise when creating a MacOS FigureManager outside the main thread)
+* :ghpull:`30697`: BUG: raise when creating a MacOS FigureManager outside the main thread
+* :ghpull:`30656`: Backport PR #29810 on branch v3.10.x (Declare free-threaded support in MacOS backend extension)
+* :ghpull:`30702`: Backport PR #30624 on branch v3.10.x (TST: Increase tolerances for Ghostscript 10.06)
+* :ghpull:`30700`: Backport PR #30698 on branch v3.10.x (BLD: update trove metadata to support py3.14)
+* :ghpull:`30624`: TST: Increase tolerances for Ghostscript 10.06
+* :ghpull:`30698`: BLD: update trove metadata to support py3.14
+* :ghpull:`30688`: Backport PR #30687 on branch v3.10.x (DOC: Fix pip link)
+* :ghpull:`30675`: Backport PR #30657 on branch v3.10.x (Fix AttributeError: module 'gi' has no attribute 'require_version')
+* :ghpull:`30674`: Backport PR #30672 on branch v3.10.x (Use pathlib.Path instead of matplotlib.path.Path in text.pyi)
+* :ghpull:`30672`: Use pathlib.Path instead of matplotlib.path.Path in text.pyi
+* :ghpull:`30657`: Fix ``AttributeError: module 'gi' has no attribute 'require_version'``
+* :ghpull:`29810`: Declare free-threaded support in MacOS backend extension
Issues (4):
-* :ghissue:`30611`: [MNT]: black version
-* :ghissue:`30551`: [Bug]: Mypy stubtest failure on disjoint_base
-* :ghissue:`30493`: [Bug]: test_save_figure_return seems flaky
-* :ghissue:`30485`: [Bug]: figures with SpanSelector(..., useblit=True) can't be saved to SVG or PDF
+* :ghissue:`30706`: [Bug]: Axes.grouped_bar() with non-string orientation (e.g., NumPy array) raises ambiguous truth-value error instead of clean ValueError
+* :ghissue:`30666`: [Bug]: calling pyplot.gca() outside the main thread crashes the interpreter with the MacOS backend
+* :ghissue:`30669`: [Bug]: Type hint for fontproperties keyword in text.pyi is wrong
+* :ghissue:`30654`: [Bug]: error plotting: AttributeError: module 'gi' has no attribute 'require_version'
Previous GitHub statistics
diff --git a/doc/users/prev_whats_new/github_stats_3.10.7.rst b/doc/users/prev_whats_new/github_stats_3.10.7.rst
new file mode 100644
index 000000000000..8c12e4050219
--- /dev/null
+++ b/doc/users/prev_whats_new/github_stats_3.10.7.rst
@@ -0,0 +1,74 @@
+.. _github-stats_3-10-7:
+
+GitHub statistics for 3.10.7 (Oct 08, 2025)
+===========================================
+
+GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/10/08
+
+These lists are automatically generated, and may be incomplete or contain duplicates.
+
+We closed 4 issues and merged 16 pull requests.
+The full list can be seen `on GitHub `__
+
+The following 32 authors contributed 422 commits.
+
+* Aasma Gupta
+* AASMA GUPTA
+* Antony Lee
+* Christine P. Chai
+* David Stansby
+* dependabot[bot]
+* Elliott Sales de Andrade
+* G.D. McBain
+* Greg Lucas
+* hannah
+* hu-xiaonan
+* Ian Thomas
+* Inês Cachola
+* Jody Klymak
+* Jouni K. Seppänen
+* Khushi_29
+* Kyle Sunden
+* Lumberbot (aka Jack)
+* N R Navaneet
+* Nathan G. Wiseman
+* Oscar Gustafsson
+* Praful Gulani
+* Qian Zhang
+* Raphael Erik Hviding
+* Roman
+* Ruth Comer
+* saikarna913
+* Scott Shambaugh
+* Thomas A Caswell
+* Tim Heap
+* Tim Hoffmann
+* Trygve Magnus Ræder
+
+GitHub issues and pull requests:
+
+Pull Requests (16):
+
+* :ghpull:`30628`: Backport PR #30626 on branch v3.10.x (MNT: Fix new F401 unused imports warnings)
+* :ghpull:`30626`: MNT: Fix new F401 unused imports warnings
+* :ghpull:`30589`: Backport PR #29745: Use PEP8 style method and function names from
+* :ghpull:`30614`: Backport PR #30612 on branch v3.10.x (MNT: update black pin)
+* :ghpull:`30612`: MNT: update black pin
+* :ghpull:`30572`: Backport PR #30571 on branch v3.10.x (CI: remove macos13)
+* :ghpull:`30571`: CI: remove macos13
+* :ghpull:`30570`: Backport PR #30558 on branch v3.10.x (Fix stubtest with mypy 18)
+* :ghpull:`30558`: Fix stubtest with mypy 18
+* :ghpull:`30540`: Backport PR #30539 on branch v3.10.x (Fix scale_unit/scale_units typo in quiver docs)
+* :ghpull:`30539`: Fix scale_unit/scale_units typo in quiver docs
+* :ghpull:`30518`: Backport PR #30497 on branch v3.10.x (TST: Use a temporary directory for test_save_figure_return)
+* :ghpull:`30497`: TST: Use a temporary directory for test_save_figure_return
+* :ghpull:`30506`: Backport PR #30490 on branch v3.10.x (Fix SVG rendering error in def update_background)
+* :ghpull:`30490`: Fix SVG rendering error in def update_background
+* :ghpull:`30494`: Backport PR #30492 on branch v3.10.x (DOC: pytz link should be from PyPI)
+
+Issues (4):
+
+* :ghissue:`30611`: [MNT]: black version
+* :ghissue:`30551`: [Bug]: Mypy stubtest failure on disjoint_base
+* :ghissue:`30493`: [Bug]: test_save_figure_return seems flaky
+* :ghissue:`30485`: [Bug]: figures with SpanSelector(..., useblit=True) can't be saved to SVG or PDF
diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst
index ea0541d5f04b..dbdcd61437ac 100644
--- a/doc/users/release_notes.rst
+++ b/doc/users/release_notes.rst
@@ -22,6 +22,7 @@ Version 3.10
../api/prev_api_changes/api_changes_3.10.1.rst
../api/prev_api_changes/api_changes_3.10.0.rst
github_stats.rst
+ prev_whats_new/github_stats_3.10.7.rst
prev_whats_new/github_stats_3.10.6.rst
prev_whats_new/github_stats_3.10.5.rst
prev_whats_new/github_stats_3.10.3.rst
diff --git a/galleries/examples/ticks/align_ticklabels.py b/galleries/examples/ticks/align_ticklabels.py
new file mode 100644
index 000000000000..ec36e0db4d07
--- /dev/null
+++ b/galleries/examples/ticks/align_ticklabels.py
@@ -0,0 +1,32 @@
+"""
+=================
+Align tick labels
+=================
+
+By default, tick labels are aligned towards the axis. This means the set of
+*y* tick labels appear right-aligned. Because the alignment reference point
+is on the axis, left-aligned tick labels would overlap the plotting area.
+To achieve a good-looking left-alignment, you have to additionally increase
+the padding.
+"""
+import matplotlib.pyplot as plt
+
+population = {
+ "Sydney": 5.2,
+ "Mexico City": 8.8,
+ "São Paulo": 12.2,
+ "Istanbul": 15.9,
+ "Lagos": 15.9,
+ "Shanghai": 21.9,
+}
+
+fig, ax = plt.subplots(layout="constrained")
+ax.barh(population.keys(), population.values())
+ax.set_xlabel('Population (in millions)')
+
+# left-align all ticklabels
+for ticklabel in ax.get_yticklabels():
+ ticklabel.set_horizontalalignment("left")
+
+# increase padding
+ax.tick_params("y", pad=70)
diff --git a/lib/matplotlib/_api/__init__.py b/lib/matplotlib/_api/__init__.py
index 22b58b62ff8e..1fc1aeb49e8f 100644
--- a/lib/matplotlib/_api/__init__.py
+++ b/lib/matplotlib/_api/__init__.py
@@ -106,6 +106,9 @@ def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
----------
values : iterable
Sequence of values to check on.
+
+ Note: All values must support == comparisons.
+ This means in particular the entries must not be numpy arrays.
_print_supported_values : bool, default: True
Whether to print *values* when raising ValueError.
**kwargs : dict
@@ -123,7 +126,18 @@ def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
if not kwargs:
raise TypeError("No argument to check!")
for key, val in kwargs.items():
- if val not in values:
+ try:
+ exists = val in values
+ except ValueError:
+ # `in` internally uses `val == values[i]`. There are some objects
+ # that do not support == to arbitrary other objects, in particular
+ # numpy arrays.
+ # Since such objects are not allowed in values, we can gracefully
+ # handle the case that val (typically provided by users) is of such
+ # type and directly state it's not in the list instead of letting
+ # the individual `val == values[i]` ValueError surface.
+ exists = False
+ if not exists:
msg = f"{val!r} is not a valid value for {key}"
if _print_supported_values:
msg += f"; supported values are {', '.join(map(repr, values))}"
diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py
index 888f5a770f5d..c27b57e36a0a 100644
--- a/lib/matplotlib/backends/backend_gtk3.py
+++ b/lib/matplotlib/backends/backend_gtk3.py
@@ -10,14 +10,14 @@
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
try:
- import gi
+ from gi import require_version as gi_require_version
except ImportError as err:
raise ImportError("The GTK3 backends require PyGObject") from err
try:
# :raises ValueError: If module/version is already loaded, already
# required, or unavailable.
- gi.require_version("Gtk", "3.0")
+ gi_require_version("Gtk", "3.0")
except ValueError as e:
# in this case we want to re-raise as ImportError so the
# auto-backend selection logic correctly skips.
diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py
index cd38968779ed..e3d3548c1cd1 100644
--- a/lib/matplotlib/backends/backend_gtk4.py
+++ b/lib/matplotlib/backends/backend_gtk4.py
@@ -9,19 +9,20 @@
KeyEvent, LocationEvent, MouseEvent, ResizeEvent, CloseEvent)
try:
- import gi
+ from gi import require_version as gi_require_version
except ImportError as err:
raise ImportError("The GTK4 backends require PyGObject") from err
try:
# :raises ValueError: If module/version is already loaded, already
# required, or unavailable.
- gi.require_version("Gtk", "4.0")
+ gi_require_version("Gtk", "4.0")
except ValueError as e:
# in this case we want to re-raise as ImportError so the
# auto-backend selection logic correctly skips.
raise ImportError(e) from e
+import gi
from gi.repository import Gio, GLib, Gtk, Gdk, GdkPixbuf
from . import _backend_gtk
from ._backend_gtk import ( # noqa: F401 # pylint: disable=W0611
diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py
index d0aded5fff63..dd642ba838af 100644
--- a/lib/matplotlib/backends/backend_qt.py
+++ b/lib/matplotlib/backends/backend_qt.py
@@ -55,7 +55,7 @@
("Key_F8", "f8"),
("Key_F9", "f9"),
("Key_F10", "f10"),
- ("Key_F10", "f11"),
+ ("Key_F11", "f11"),
("Key_F12", "f12"),
("Key_Super_L", "super"),
("Key_Super_R", "super"),
diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py
index f04604c14cce..81b7f9cdc96c 100644
--- a/lib/matplotlib/tests/test_api.py
+++ b/lib/matplotlib/tests/test_api.py
@@ -150,3 +150,8 @@ def f() -> None:
def test_empty_check_in_list() -> None:
with pytest.raises(TypeError, match="No argument to check!"):
_api.check_in_list(["a"])
+
+
+def test_check_in_list_numpy() -> None:
+ with pytest.raises(ValueError, match=r"array\(5\) is not a valid value"):
+ _api.check_in_list(['a', 'b'], value=np.array(5))
diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py
index 8e50ddf84024..0648e43cde94 100644
--- a/lib/matplotlib/tests/test_backend_macosx.py
+++ b/lib/matplotlib/tests/test_backend_macosx.py
@@ -1,18 +1,19 @@
import os
+import threading
+from pathlib import Path
import pytest
from unittest import mock
import matplotlib as mpl
import matplotlib.pyplot as plt
-try:
- from matplotlib.backends import _macosx
-except ImportError:
- pytest.skip("These are mac only tests", allow_module_level=True)
+from matplotlib.testing import subprocess_run_helper
-@pytest.mark.backend('macosx')
-def test_cached_renderer():
+_test_timeout = 60
+
+
+def _test_cached_renderer():
# Make sure that figures have an associated renderer after
# a fig.canvas.draw() call
fig = plt.figure(1)
@@ -24,8 +25,14 @@ def test_cached_renderer():
assert fig.canvas.get_renderer()._renderer is not None
-@pytest.mark.backend('macosx')
-def test_savefig_rcparam(monkeypatch, tmp_path):
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_cached_renderer():
+ subprocess_run_helper(_test_cached_renderer, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"})
+
+
+def _test_savefig_rcparam():
+ tmp_path = Path(os.environ["TEST_SAVEFIG_PATH"])
def new_choose_save_file(title, directory, filename):
# Replacement function instead of opening a GUI window
@@ -34,9 +41,10 @@ def new_choose_save_file(title, directory, filename):
os.makedirs(f"{directory}/test")
return f"{directory}/test/{filename}"
- monkeypatch.setattr(_macosx, "choose_save_file", new_choose_save_file)
fig = plt.figure()
- with mpl.rc_context({"savefig.directory": tmp_path}):
+ with (mock.patch("matplotlib.backends._macosx.choose_save_file",
+ new_choose_save_file),
+ mpl.rc_context({"savefig.directory": tmp_path})):
fig.canvas.toolbar.save_figure()
# Check the saved location got created
save_file = f"{tmp_path}/test/{fig.canvas.get_default_filename()}"
@@ -47,14 +55,20 @@ def new_choose_save_file(title, directory, filename):
assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test"
-@pytest.mark.backend('macosx')
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_savefig_rcparam(tmp_path):
+ subprocess_run_helper(
+ _test_savefig_rcparam, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx", "TEST_SAVEFIG_PATH": tmp_path})
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
def test_ipython():
from matplotlib.testing import ipython_in_subprocess
ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"})
-@pytest.mark.backend('macosx')
-def test_save_figure_return():
+def _test_save_figure_return():
fig, ax = plt.subplots()
ax.imshow([[1]])
prop = "matplotlib.backends._macosx.choose_save_file"
@@ -65,3 +79,31 @@ def test_save_figure_return():
with mock.patch(prop, return_value=None):
fname = fig.canvas.manager.toolbar.save_figure()
assert fname is None
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_save_figure_return():
+ subprocess_run_helper(_test_save_figure_return, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"})
+
+
+def _test_create_figure_on_worker_thread_fails():
+ def create_figure():
+ warn_msg = "Matplotlib GUI outside of the main thread will likely fail."
+ err_msg = "Cannot create a GUI FigureManager outside the main thread"
+ with pytest.warns(UserWarning, match=warn_msg):
+ with pytest.raises(RuntimeError, match=err_msg):
+ plt.gcf()
+
+ worker = threading.Thread(target=create_figure)
+ worker.start()
+ worker.join()
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_create_figure_on_worker_thread_fails():
+ subprocess_run_helper(
+ _test_create_figure_on_worker_thread_fails,
+ timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"}
+ )
diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py
index 7a2809931757..9965c914daa4 100644
--- a/lib/matplotlib/tests/test_bbox_tight.py
+++ b/lib/matplotlib/tests/test_bbox_tight.py
@@ -108,7 +108,7 @@ def test_bbox_inches_tight_clipping():
plt.gcf().artists.append(patch)
-@image_comparison(['bbox_inches_tight_raster'],
+@image_comparison(['bbox_inches_tight_raster'], tol=0.15, # For Ghostscript 10.06+.
remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
def test_bbox_inches_tight_raster():
"""Test rasterization with tight_layout"""
diff --git a/lib/matplotlib/tests/test_doc.py b/lib/matplotlib/tests/test_doc.py
index 3e28fd1b8eb7..f3d6d6e3fd5d 100644
--- a/lib/matplotlib/tests/test_doc.py
+++ b/lib/matplotlib/tests/test_doc.py
@@ -7,9 +7,9 @@ def test_sphinx_gallery_example_header():
This test monitors that the version we have copied is still the same as
the EXAMPLE_HEADER in sphinx-gallery. If sphinx-gallery changes its
EXAMPLE_HEADER, this test will start to fail. In that case, please update
- the monkey-patching of EXAMPLE_HEADER in conf.py.
+ the monkey-patching of EXAMPLE_HEADER in sphinxext/util.py.
"""
- pytest.importorskip('sphinx_gallery', minversion='0.16.0')
+ pytest.importorskip('sphinx_gallery', minversion='0.20.0')
from sphinx_gallery import gen_rst
EXAMPLE_HEADER = """
@@ -25,7 +25,7 @@ def test_sphinx_gallery_example_header():
:class: sphx-glr-download-link-note
:ref:`Go to the end `
- to download the full example code.{2}
+ to download the full example code{2}
.. rst-class:: sphx-glr-example-title
diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py
index 5373ed121560..0d1d30291328 100644
--- a/lib/matplotlib/tests/test_image.py
+++ b/lib/matplotlib/tests/test_image.py
@@ -37,7 +37,7 @@ def test_alpha_interp():
axr.imshow(img, interpolation="bilinear")
-@image_comparison(['interp_nearest_vs_none'],
+@image_comparison(['interp_nearest_vs_none'], tol=3.7, # For Ghostscript 10.06+.
extensions=['pdf', 'svg'], remove_text=True)
def test_interp_nearest_vs_none():
"""Test the effect of "nearest" and "none" interpolation"""
diff --git a/lib/matplotlib/text.pyi b/lib/matplotlib/text.pyi
index d65a3dc4c7da..fc1ab1c416cf 100644
--- a/lib/matplotlib/text.pyi
+++ b/lib/matplotlib/text.pyi
@@ -2,7 +2,7 @@ from .artist import Artist
from .backend_bases import RendererBase
from .font_manager import FontProperties
from .offsetbox import DraggableAnnotation
-from .path import Path
+from pathlib import Path
from .patches import FancyArrowPatch, FancyBboxPatch
from .textpath import ( # noqa: F401, reexported API
TextPath as TextPath,
diff --git a/pyproject.toml b/pyproject.toml
index 25f4ce8d4948..23c441b52c9c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,6 +20,7 @@ classifiers=[
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Visualization",
]
diff --git a/src/_macosx.m b/src/_macosx.m
index c8e2cea43863..fcc221cf6fd5 100755
--- a/src/_macosx.m
+++ b/src/_macosx.m
@@ -580,6 +580,16 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name)
static PyObject*
FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
+ if (![NSThread isMainThread]) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Cannot create a GUI FigureManager outside the main thread "
+ "using the MacOS backend. Use a non-interactive "
+ "backend like 'agg' to make plots on worker threads."
+ );
+ return NULL;
+ }
+
lazy_init();
Window* window = [Window alloc];
if (!window) { return NULL; }
@@ -1895,6 +1905,9 @@ - (void)flagsChanged:(NSEvent *)event
Py_XDECREF(m);
return NULL;
}
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
return m;
}
diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py
index 07b67a3e04ee..2ee72c6a89fa 100644
--- a/tools/cache_zenodo_svg.py
+++ b/tools/cache_zenodo_svg.py
@@ -63,6 +63,7 @@ def _get_xdg_cache_dir():
if __name__ == "__main__":
data = {
+ "v3.10.8": "17595503",
"v3.10.7": "17298696",
"v3.10.6": "16999430",
"v3.10.5": "16644850",