diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 1c4076d6bb3a..84951cac4784 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -47,7 +47,7 @@ jobs: - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==1.9.0 + python -m pip install cibuildwheel==2.1.1 - name: Build minimum NumPy for aarch64 if: matrix.cibw_archs == 'aarch64' && steps.numpy-cache.outputs.cache-hit != 'true' @@ -93,7 +93,7 @@ jobs: CIBW_BUILD: "pp37-*" CIBW_BEFORE_BUILD: pip install certifi numpy==${{ env.min-numpy-version }} CIBW_ARCHS: ${{ matrix.cibw_archs }} - if: runner.os != 'Windows' && matrix.cibw_archs != 'aarch64' + if: matrix.cibw_archs != 'aarch64' - name: Validate that LICENSE files are included in wheels run: | diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d7be356d66d2..00e99c9f073e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1618,14 +1618,20 @@ def wrapper(*args, **kwargs): if frame is None: # when called in embedded context may hit frame is None. break + # Work around sphinx-gallery not setting __name__. + frame_name = frame.f_globals.get('__name__', '') if re.match(r'\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))', - # Work around sphinx-gallery not setting __name__. - frame.f_globals.get('__name__', '')): - if public_api.match(frame.f_code.co_name): - name = frame.f_code.co_name + frame_name): + name = frame.f_code.co_name + if public_api.match(name): if name in ('print_figure', '_no_output_draw'): seen_print_figure = True + elif frame_name == '_functools': + # PyPy adds an extra frame without module prefix for this + # functools wrapper, which we ignore to assume we're still in + # Matplotlib code. + continue else: break diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 3fbf4ee245f7..5e053cb364a3 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1451,7 +1451,8 @@ def _make_verts(self): docstring.interpd.update( - FancyArrow="\n".join(inspect.getdoc(FancyArrow.__init__).splitlines()[2:])) + FancyArrow="\n".join( + (inspect.getdoc(FancyArrow.__init__) or "").splitlines()[2:])) class CirclePolygon(RegularPolygon): diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 180906106747..306a60aa4e66 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -620,10 +620,11 @@ def _get_scale_docs(): """ docs = [] for name, scale_class in _scale_mapping.items(): + docstring = inspect.getdoc(scale_class.__init__) or "" docs.extend([ f" {name!r}", "", - textwrap.indent(inspect.getdoc(scale_class.__init__), " " * 8), + textwrap.indent(docstring, " " * 8), "" ]) return "\n".join(docs) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index fbd984f70543..a7ba927494b1 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -1,6 +1,6 @@ -import gc import os from pathlib import Path +import platform import subprocess import sys import weakref @@ -87,10 +87,15 @@ def test_null_movie_writer(anim): @pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) def test_animation_delete(anim): + if platform.python_implementation() == 'PyPy': + # Something in the test setup fixture lingers around into the test and + # breaks pytest.warns on PyPy. This garbage collection fixes it. + # https://foss.heptapod.net/pypy/pypy/-/issues/3536 + np.testing.break_cycles() anim = animation.FuncAnimation(**anim) with pytest.warns(Warning, match='Animation was deleted'): del anim - gc.collect() + np.testing.break_cycles() def test_movie_writer_dpi_default(): @@ -201,6 +206,11 @@ def test_save_animation_smoketest(tmpdir, writer, frame_format, output, anim): ]) @pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) def test_animation_repr_html(writer, html, want, anim): + if platform.python_implementation() == 'PyPy': + # Something in the test setup fixture lingers around into the test and + # breaks pytest.warns on PyPy. This garbage collection fixes it. + # https://foss.heptapod.net/pypy/pypy/-/issues/3536 + np.testing.break_cycles() if (writer == 'imagemagick' and html == 'html5' # ImageMagick delegates to ffmpeg for this format. and not animation.FFMpegWriter.isAvailable()): @@ -214,7 +224,8 @@ def test_animation_repr_html(writer, html, want, anim): if want is None: assert html is None with pytest.warns(UserWarning): - del anim # Animtion was never run, so will warn on cleanup. + del anim # Animation was never run, so will warn on cleanup. + np.testing.break_cycles() else: assert want in html @@ -324,6 +335,7 @@ def frames_generator(): writer = NullMovieWriter() anim.save('unused.null', writer=writer) assert len(frames_generated) == 5 + np.testing.break_cycles() for f in frames_generated: # If cache_frame_data is True, then the weakref should be alive; # if cache_frame_data is False, then the weakref should be dead (None). diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 771d5193e397..5218d2c776a3 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -1,6 +1,7 @@ import functools import inspect import os +import platform import re import subprocess import sys @@ -111,6 +112,9 @@ def legitimate_quit(): print("success") +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason='PyPy does not support Tkinter threading: ' + 'https://foss.heptapod.net/pypy/pypy/-/issues/1929') @pytest.mark.backend('TkAgg', skip_on_importerror=True) @pytest.mark.flaky(reruns=3) @_isolated_tk_test(success_count=1) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index d3e04e986b0c..36ec3bfc83c3 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -3,6 +3,7 @@ import inspect import json import os +import platform import signal import subprocess import sys @@ -220,6 +221,12 @@ def _test_thread_impl(): elif param.values[0].get("QT_API") == "PySide2": param.marks.append( pytest.mark.xfail(raises=subprocess.CalledProcessError)) + elif backend == "tkagg" and platform.python_implementation() != 'CPython': + param.marks.append( + pytest.mark.xfail( + reason='PyPy does not support Tkinter threading: ' + 'https://foss.heptapod.net/pypy/pypy/-/issues/1929', + strict=True)) @pytest.mark.parametrize("env", _thread_safe_backends) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 474ea1a41d93..88fac2dee435 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -198,11 +198,13 @@ def count(self): return count1 def is_empty(self): + np.testing.break_cycles() assert self.callbacks._func_cid_map == {} assert self.callbacks.callbacks == {} assert self.callbacks._pickled_cids == set() def is_not_empty(self): + np.testing.break_cycles() assert self.callbacks._func_cid_map != {} assert self.callbacks.callbacks != {} diff --git a/lib/matplotlib/tests/test_quiver.py b/lib/matplotlib/tests/test_quiver.py index d7a848f61bcc..ca0bf19de5e7 100644 --- a/lib/matplotlib/tests/test_quiver.py +++ b/lib/matplotlib/tests/test_quiver.py @@ -1,6 +1,9 @@ +import platform +import sys + import numpy as np import pytest -import sys + from matplotlib import pyplot as plt from matplotlib.testing.decorators import image_comparison @@ -15,6 +18,8 @@ def draw_quiver(ax, **kw): return Q +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason='Requires CPython') def test_quiver_memory_leak(): fig, ax = plt.subplots() @@ -27,6 +32,8 @@ def test_quiver_memory_leak(): assert sys.getrefcount(ttX) == 2 +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason='Requires CPython') def test_quiver_key_memory_leak(): fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index aea09462b197..5ed115abb68f 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -1,9 +1,9 @@ from contextlib import contextmanager -import gc from pathlib import Path from tempfile import TemporaryDirectory import sys +import numpy as np import pytest import matplotlib as mpl @@ -165,7 +165,7 @@ def test_xkcd_no_cm(): assert mpl.rcParams["path.sketch"] is None plt.xkcd() assert mpl.rcParams["path.sketch"] == (1, 100, 2) - gc.collect() + np.testing.break_cycles() assert mpl.rcParams["path.sketch"] == (1, 100, 2) diff --git a/src/_c_internal_utils.c b/src/_c_internal_utils.c index 532824c5d05b..7209b4f396fc 100644 --- a/src/_c_internal_utils.c +++ b/src/_c_internal_utils.c @@ -68,7 +68,13 @@ mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module) wchar_t* appid = NULL; HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); if (FAILED(hr)) { +#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030600 + /* Remove when we require PyPy 7.3.6 */ + PyErr_SetFromWindowsErr(hr); + return NULL; +#else return PyErr_SetFromWindowsErr(hr); +#endif } PyObject* py_appid = PyUnicode_FromWideChar(appid, -1); CoTaskMemFree(appid); @@ -89,7 +95,13 @@ mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg) HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); PyMem_Free(appid); if (FAILED(hr)) { +#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030600 + /* Remove when we require PyPy 7.3.6 */ + PyErr_SetFromWindowsErr(hr); + return NULL; +#else return PyErr_SetFromWindowsErr(hr); +#endif } Py_RETURN_NONE; #else
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: