From 4e46319444e32faf637d8547a6266c7e3a4be0cd Mon Sep 17 00:00:00 2001 From: James Tocknell Date: Sun, 4 Sep 2016 23:17:37 +1000 Subject: [PATCH 1/4] Added tests for PEP 519/pathlib, these should fail --- lib/matplotlib/tests/test_animation.py | 92 +++++++++++++++++++ lib/matplotlib/tests/test_backend_pdf.py | 37 ++++++++ lib/matplotlib/tests/test_cbook.py | 33 +++++++ lib/matplotlib/tests/test_figure.py | 107 +++++++++++++++++++++++ lib/matplotlib/tests/test_image.py | 38 +++++++- 5 files changed, 305 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index c6373584ce2d..ced59bec2188 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -9,6 +9,7 @@ import numpy as np from numpy.testing import assert_equal from nose.tools import assert_false, assert_true +from nose.plugins.skip import SkipTest import matplotlib as mpl from matplotlib import pyplot as plt from matplotlib import animation @@ -107,6 +108,12 @@ def test_save_animation_smoketest(): for writer, extension in six.iteritems(WRITER_OUTPUT): yield check_save_animation, writer, extension + for writer, extension in six.iteritems(WRITER_OUTPUT): + yield check_save_animation_pep_519, writer, extension + + for writer, extension in six.iteritems(WRITER_OUTPUT): + yield check_save_animation_pathlib, writer, extension + @cleanup def check_save_animation(writer, extension='mp4'): @@ -144,6 +151,91 @@ def animate(i): pass +@cleanup +def check_save_animation_pep_519(writer, extension='mp4'): + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + if not animation.writers.is_available(writer): + skip("writer '%s' not available on this system" + % writer) + fig, ax = plt.subplots() + line, = ax.plot([], []) + + ax.set_xlim(0, 10) + ax.set_ylim(-1, 1) + + def init(): + line.set_data([], []) + return line, + + def animate(i): + x = np.linspace(0, 10, 100) + y = np.sin(x + i) + line.set_data(x, y) + return line, + + # Use NamedTemporaryFile: will be automatically deleted + F = tempfile.NamedTemporaryFile(suffix='.' + extension) + F.close() + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) + try: + anim.save(FakeFSPathClass(F.name), fps=30, writer=writer, bitrate=500) + except UnicodeDecodeError: + xfail("There can be errors in the numpy import stack, " + "see issues #1891 and #2679") + finally: + try: + os.remove(F.name) + except Exception: + pass + + +@cleanup +def check_save_animation_pathlib(writer, extension='mp4'): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + + if not animation.writers.is_available(writer): + skip("writer '%s' not available on this system" % writer) + fig, ax = plt.subplots() + line, = ax.plot([], []) + + ax.set_xlim(0, 10) + ax.set_ylim(-1, 1) + + def init(): + line.set_data([], []) + return line, + + def animate(i): + x = np.linspace(0, 10, 100) + y = np.sin(x + i) + line.set_data(x, y) + return line, + + # Use NamedTemporaryFile: will be automatically deleted + F = tempfile.NamedTemporaryFile(suffix='.' + extension) + F.close() + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) + try: + anim.save(Path(F.name), fps=30, writer=writer, bitrate=500) + except UnicodeDecodeError: + xfail("There can be errors in the numpy import stack, " + "see issues #1891 and #2679") + finally: + try: + os.remove(F.name) + except Exception: + pass + + @cleanup def test_no_length_frames(): fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 2feee6fb1238..923294c5cce2 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -14,6 +14,7 @@ from matplotlib import pyplot as plt from matplotlib.testing.decorators import (image_comparison, knownfailureif, cleanup) +from nose.plugins.skip import SkipTest if 'TRAVIS' not in os.environ: @image_comparison(baseline_images=['pdf_use14corefonts'], @@ -132,3 +133,39 @@ def test_grayscale_alpha(): ax.imshow(dd, interpolation='none', cmap='gray_r') ax.set_xticks([]) ax.set_yticks([]) + + +@cleanup +def test_pdfpages_accept_pep_519(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + with PdfPages(FakeFSPathClass(tmpfile.name)) as pdf: + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + pdf.savefig(fig) + + +@cleanup +def test_savefig_accept_pathlib(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + with PdfPages(Path(tmpfile.name)) as pdf: + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + pdf.savefig(fig) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 7560f5ebf861..6b6cba388234 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -13,6 +13,8 @@ assert_array_almost_equal) from nose.tools import (assert_equal, assert_not_equal, raises, assert_true, assert_raises) +from nose.plugins.skip import SkipTest +from matplotlib.testing.decorators import cleanup import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -523,3 +525,34 @@ def test_flatiter(): assert 0 == next(it) assert 1 == next(it) + + +@cleanup +def test_to_filehandle_accept_pep_519(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + tmpfile = NamedTemporaryFile(delete=False) + tmpfile.close() + pep519_path = FakeFSPathClass(tmpfile.name) + cbook.to_filehandle(pep519_path) + + +@cleanup +def test_to_filehandle_accept_pathlib(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + tmpfile = NamedTemporaryFile(delete=False) + tmpfile.close() + path = Path(tmpfile.name) + cbook.to_filehandle(path) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index ba45f133961d..049ccdf3149b 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -5,6 +5,8 @@ from six.moves import xrange from nose.tools import assert_equal, assert_true +from nose.plugins.skip import SkipTest + from matplotlib import rcParams from matplotlib.testing.decorators import image_comparison, cleanup from matplotlib.axes import Axes @@ -205,6 +207,111 @@ def test_figaspect(): assert h / w == 1 +@cleanup +def test_savefig_accept_pep_519_png(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + + tmpfile = NamedTemporaryFile(suffix='.png') + tmpfile.close() + pep519_path = FakeFSPathClass(tmpfile.name) + fig.savefig(pep519_path) + + +@cleanup +def test_savefig_accept_pathlib_png(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.png') + tmpfile.close() + path = Path(tmpfile.name) + fig.savefig(path) + + +@cleanup +def test_savefig_accept_pep_519_svg(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.svg') + tmpfile.close() + pep519_path = FakeFSPathClass(tmpfile.name) + fig.savefig(pep519_path) + + +@cleanup +def test_savefig_accept_pathlib_svg(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.svg') + tmpfile.close() + path = Path(tmpfile.name) + fig.savefig(path) + + +@cleanup +def test_savefig_accept_pep_519_pdf(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + pep519_path = FakeFSPathClass(tmpfile.name) + fig.savefig(pep519_path) + + +@cleanup +def test_savefig_accept_pathlib_pdf(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + path = Path(tmpfile.name) + fig.savefig(path) + if __name__ == "__main__": import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 851f9cffb775..b204744eb7d5 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -6,6 +6,7 @@ import os from nose.plugins.attrib import attr +from nose.plugins.skip import SkipTest import numpy as np @@ -752,5 +753,38 @@ def test_imshow_endianess(): ax2.imshow(Z.astype('>f8'), **kwargs) -if __name__ == '__main__': - nose.runmodule(argv=['-s', '--with-doctest'], exit=False) +@cleanup +def test_imsave_accept_pep_519(): + from tempfile import NamedTemporaryFile + + class FakeFSPathClass(object): + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + a = np.array([[1, 2], [3, 4]]) + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + pep519_path = FakeFSPathClass(tmpfile.name) + plt.imsave(pep519_path, a) + + +@cleanup +def test_imsave_accept_pathlib(): + try: + from pathlib import Path + except ImportError: + raise SkipTest("pathlib not installed") + from tempfile import NamedTemporaryFile + + a = np.array([[1, 2], [3, 4]]) + tmpfile = NamedTemporaryFile(suffix='.pdf') + tmpfile.close() + path = Path(tmpfile.name) + plt.imsave(path, a) + + +if __name__=='__main__': + nose.runmodule(argv=['-s','--with-doctest'], exit=False) From 1414c2853b197c7865a8737165f98badb3975f7f Mon Sep 17 00:00:00 2001 From: James Tocknell Date: Sun, 4 Sep 2016 23:17:37 +1000 Subject: [PATCH 2/4] Add PEP 519 support --- lib/matplotlib/animation.py | 3 +- lib/matplotlib/backends/backend_agg.py | 13 +++++-- lib/matplotlib/backends/backend_pdf.py | 3 +- lib/matplotlib/cbook.py | 53 ++++++++++++++++++++++++-- lib/matplotlib/image.py | 2 +- 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index b6150ae3a694..bb542c05eff2 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -37,7 +37,7 @@ import contextlib import tempfile import warnings -from matplotlib.cbook import iterable, is_string_like +from matplotlib.cbook import iterable, is_string_like, fspath_no_except from matplotlib.compat import subprocess from matplotlib import verbose from matplotlib import rcParams, rcParamsDefault, rc_context @@ -281,6 +281,7 @@ def _run(self): output = sys.stdout else: output = subprocess.PIPE + command = [fspath_no_except(cmd) for cmd in command] verbose.report('MovieWriter.run: running command: %s' % ' '.join(command)) self._proc = subprocess.Popen(command, shell=False, diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 587e09e9534d..80b51fade9f1 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -30,7 +30,8 @@ from matplotlib import verbose, rcParams from matplotlib.backend_bases import (RendererBase, FigureManagerBase, FigureCanvasBase) -from matplotlib.cbook import is_string_like, maxdict, restrict_dict +from matplotlib.cbook import (is_string_like, maxdict, restrict_dict, + fspath_no_except, to_filehandle) from matplotlib.figure import Figure from matplotlib.font_manager import findfont, get_font from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, @@ -553,7 +554,10 @@ def print_png(self, filename_or_obj, *args, **kwargs): close = False try: - _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi) + _png.write_png( + renderer._renderer, fspath_no_except(filename_or_obj), + self.figure.dpi + ) finally: if close: filename_or_obj.close() @@ -606,7 +610,8 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): if 'quality' not in options: options['quality'] = rcParams['savefig.jpeg_quality'] - return background.save(filename_or_obj, format='jpeg', **options) + return background.save(to_filehandle(filename_or_obj), format='jpeg', + **options) print_jpeg = print_jpg # add TIFF support @@ -616,7 +621,7 @@ def print_tif(self, filename_or_obj, *args, **kwargs): return image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) dpi = (self.figure.dpi, self.figure.dpi) - return image.save(filename_or_obj, format='tiff', + return image.save(to_filehandle(filename_or_obj), format='tiff', dpi=dpi) print_tiff = print_tif diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 21719273c423..47591f91fc96 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -34,7 +34,7 @@ FigureManagerBase, FigureCanvasBase) from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.cbook import (Bunch, is_string_like, get_realpath_and_stat, - is_writable_file_like, maxdict) + is_writable_file_like, maxdict, fspath_no_except) from matplotlib.figure import Figure from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font from matplotlib.afm import AFM @@ -429,6 +429,7 @@ def __init__(self, filename): self.passed_in_file_object = False self.original_file_like = None self.tell_base = 0 + filename = fspath_no_except(filename) if is_string_like(filename): fh = open(filename, 'wb') elif is_writable_file_like(filename): diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 0e8c0f50e150..455736b09c67 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -756,6 +756,7 @@ def to_filehandle(fname, flag='rU', return_opened=False): files is automatic, if the filename ends in .gz. *flag* is a read/write flag for :func:`file` """ + fname = fspath_no_except(fname) if is_string_like(fname): if fname.endswith('.gz'): # get rid of 'U' in flag for gzipped files. @@ -815,6 +816,7 @@ def get_sample_data(fname, asfileobj=True): root = matplotlib.rcParams['examples.directory'] else: root = os.path.join(matplotlib._get_data_path(), 'sample_data') + fname = fspath_no_except(fname) path = os.path.join(root, fname) if asfileobj: @@ -1019,6 +1021,7 @@ def __init__(self): self._cache = {} def __call__(self, path): + path = fspath_no_except(path) result = self._cache.get(path) if result is None: realpath = os.path.realpath(path) @@ -1173,7 +1176,7 @@ def listFiles(root, patterns='*', recurse=1, return_folders=0): pattern_list = patterns.split(';') results = [] - for dirname, dirs, files in os.walk(root): + for dirname, dirs, files in os.walk(fspath_no_except(root)): # Append to results all relevant files (and perhaps folders) for name in files: fullname = os.path.normpath(os.path.join(dirname, name)) @@ -1197,10 +1200,10 @@ def get_recursive_filelist(args): files = [] for arg in args: - if os.path.isfile(arg): + if os.path.isfile(fspath_no_except(arg)): files.append(arg) continue - if os.path.isdir(arg): + if os.path.isdir(fspath_no_except(arg)): newfiles = listFiles(arg, recurse=1, return_folders=1) files.extend(newfiles) @@ -1726,6 +1729,7 @@ def simple_linear_interpolation(a, steps): def recursive_remove(path): + path = fspath_no_except(path) if os.path.isdir(path): for fname in (glob.glob(os.path.join(path, '*')) + glob.glob(os.path.join(path, '.*'))): @@ -2666,7 +2670,7 @@ class TimeoutError(RuntimeError): pass def __init__(self, path): - self.path = path + self.path = fspath_no_except(path) self.end = "-" + str(os.getpid()) self.lock_path = os.path.join(self.path, self.LOCKFN + self.end) self.pattern = os.path.join(self.path, self.LOCKFN + '-*') @@ -2701,3 +2705,44 @@ def __exit__(self, exc_type, exc_value, traceback): os.rmdir(path) except OSError: pass + + +try: + from os import fspath +except ImportError: + def fspath(path): + """ + Return the string representation of the path. + If str or bytes is passed in, it is returned unchanged. + This code comes from PEP 519, modified to support earlier versions of + python. + + This is required for python < 3.6. + """ + if isinstance(path, (six.text_type, six.binary_type)): + return path + + # Work from the object's type to match method resolution of other magic + # methods. + path_type = type(path) + try: + return path_type.__fspath__(path) + except AttributeError: + if hasattr(path_type, '__fspath__'): + raise + try: + import pathlib + except ImportError: + pass + else: + if isinstance(path, pathlib.PurePath): + return six.text_type(path) + + raise TypeError("expected str, bytes or os.PathLike object, not " + + path_type.__name__) + +def fspath_no_except(path): + try: + return fspath(path) + except TypeError: + return path diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 9e1646f9bc94..c4f2e0f924f0 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -525,7 +525,7 @@ def contains(self, mouseevent): def write_png(self, fname): """Write the image to png file with fname""" im = self.to_rgba(self._A, bytes=True, norm=False) - _png.write_png(im, fname) + _png.write_png(im, cbook.fspath_no_except(fname)) def set_data(self, A): """ From 3cdf59081ee8132edd72f796a8ad748c46e946e8 Mon Sep 17 00:00:00 2001 From: James Tocknell Date: Wed, 7 Sep 2016 19:12:17 +1000 Subject: [PATCH 3/4] Use skip instead of SkipTest for joint nose/pytest support --- lib/matplotlib/tests/test_animation.py | 3 +-- lib/matplotlib/tests/test_backend_pdf.py | 4 ++-- lib/matplotlib/tests/test_cbook.py | 4 ++-- lib/matplotlib/tests/test_figure.py | 8 ++++---- lib/matplotlib/tests/test_image.py | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index ced59bec2188..8225fbf6f4ea 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -9,7 +9,6 @@ import numpy as np from numpy.testing import assert_equal from nose.tools import assert_false, assert_true -from nose.plugins.skip import SkipTest import matplotlib as mpl from matplotlib import pyplot as plt from matplotlib import animation @@ -200,7 +199,7 @@ def check_save_animation_pathlib(writer, extension='mp4'): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") if not animation.writers.is_available(writer): skip("writer '%s' not available on this system" % writer) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 923294c5cce2..5fd2fb4e9f90 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -14,7 +14,7 @@ from matplotlib import pyplot as plt from matplotlib.testing.decorators import (image_comparison, knownfailureif, cleanup) -from nose.plugins.skip import SkipTest +from matplotlib.testing import skip if 'TRAVIS' not in os.environ: @image_comparison(baseline_images=['pdf_use14corefonts'], @@ -158,7 +158,7 @@ def test_savefig_accept_pathlib(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 6b6cba388234..4fe48dd42b22 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -13,8 +13,8 @@ assert_array_almost_equal) from nose.tools import (assert_equal, assert_not_equal, raises, assert_true, assert_raises) -from nose.plugins.skip import SkipTest from matplotlib.testing.decorators import cleanup +from matplotlib.testing import skip import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -549,7 +549,7 @@ def test_to_filehandle_accept_pathlib(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile tmpfile = NamedTemporaryFile(delete=False) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 049ccdf3149b..e33d77e1f184 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -5,10 +5,10 @@ from six.moves import xrange from nose.tools import assert_equal, assert_true -from nose.plugins.skip import SkipTest from matplotlib import rcParams from matplotlib.testing.decorators import image_comparison, cleanup +from matplotlib.testing import skip from matplotlib.axes import Axes import matplotlib.pyplot as plt import numpy as np @@ -232,7 +232,7 @@ def test_savefig_accept_pathlib_png(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile fig, ax = plt.subplots() @@ -267,7 +267,7 @@ def test_savefig_accept_pathlib_svg(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile fig, ax = plt.subplots() @@ -302,7 +302,7 @@ def test_savefig_accept_pathlib_pdf(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index b204744eb7d5..e3690e1497f3 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -6,10 +6,10 @@ import os from nose.plugins.attrib import attr -from nose.plugins.skip import SkipTest import numpy as np +from matplotlib.testing import skip from matplotlib.testing.decorators import (image_comparison, knownfailureif, cleanup) from matplotlib.image import (BboxImage, imread, NonUniformImage, @@ -776,7 +776,7 @@ def test_imsave_accept_pathlib(): try: from pathlib import Path except ImportError: - raise SkipTest("pathlib not installed") + skip("pathlib not installed") from tempfile import NamedTemporaryFile a = np.array([[1, 2], [3, 4]]) From 76bb608c97776473c3b1c0f987c4e172cd58b386 Mon Sep 17 00:00:00 2001 From: James Tocknell Date: Wed, 7 Sep 2016 22:25:34 +1000 Subject: [PATCH 4/4] Added closed_tempfile for testing filename handling --- lib/matplotlib/testing/__init__.py | 20 +++++++++ lib/matplotlib/tests/test_animation.py | 53 +++++------------------ lib/matplotlib/tests/test_backend_pdf.py | 37 ++++++---------- lib/matplotlib/tests/test_cbook.py | 19 +++----- lib/matplotlib/tests/test_figure.py | 51 ++++++++-------------- lib/matplotlib/tests/test_font_manager.py | 13 ++---- lib/matplotlib/tests/test_image.py | 19 +++----- lib/matplotlib/tests/test_testing.py | 21 +++++++++ 8 files changed, 102 insertions(+), 131 deletions(-) create mode 100644 lib/matplotlib/tests/test_testing.py diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 0b549b36d088..228ff7ff2b35 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -4,6 +4,8 @@ import inspect import warnings from contextlib import contextmanager +import shutil +import tempfile import matplotlib from matplotlib.cbook import is_string_like, iterable @@ -164,3 +166,21 @@ def setup(): rcdefaults() # Start with all defaults set_font_settings_for_testing() + + +@contextmanager +def closed_tempfile(suffix='', text=None): + """ + Context manager which yields the path to a closed temporary file with the + suffix `suffix`. The file will be deleted on exiting the context. An + additional argument `text` can be provided to have the file contain `text`. + """ + with tempfile.NamedTemporaryFile( + 'w+t', suffix=suffix, delete=False + ) as test_file: + file_name = test_file.name + if text is not None: + test_file.write(text) + test_file.flush() + yield file_name + shutil.rmtree(file_name, ignore_errors=True) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index 8225fbf6f4ea..c75100ba8658 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -12,7 +12,7 @@ import matplotlib as mpl from matplotlib import pyplot as plt from matplotlib import animation -from ..testing import xfail, skip +from ..testing import xfail, skip, closed_tempfile from ..testing.decorators import cleanup @@ -134,20 +134,9 @@ def animate(i): line.set_data(x, y) return line, - # Use NamedTemporaryFile: will be automatically deleted - F = tempfile.NamedTemporaryFile(suffix='.' + extension) - F.close() - anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) - try: - anim.save(F.name, fps=30, writer=writer, bitrate=500) - except UnicodeDecodeError: - xfail("There can be errors in the numpy import stack, " - "see issues #1891 and #2679") - finally: - try: - os.remove(F.name) - except Exception: - pass + with closed_tempfile(suffix='.' + extension) as fname: + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) + anim.save(fname, fps=30, writer=writer, bitrate=500) @cleanup @@ -178,20 +167,9 @@ def animate(i): line.set_data(x, y) return line, - # Use NamedTemporaryFile: will be automatically deleted - F = tempfile.NamedTemporaryFile(suffix='.' + extension) - F.close() - anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) - try: - anim.save(FakeFSPathClass(F.name), fps=30, writer=writer, bitrate=500) - except UnicodeDecodeError: - xfail("There can be errors in the numpy import stack, " - "see issues #1891 and #2679") - finally: - try: - os.remove(F.name) - except Exception: - pass + with closed_tempfile(suffix='.' + extension) as fname: + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) + anim.save(FakeFSPathClass(fname), fps=30, writer=writer, bitrate=500) @cleanup @@ -219,20 +197,9 @@ def animate(i): line.set_data(x, y) return line, - # Use NamedTemporaryFile: will be automatically deleted - F = tempfile.NamedTemporaryFile(suffix='.' + extension) - F.close() - anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) - try: - anim.save(Path(F.name), fps=30, writer=writer, bitrate=500) - except UnicodeDecodeError: - xfail("There can be errors in the numpy import stack, " - "see issues #1891 and #2679") - finally: - try: - os.remove(F.name) - except Exception: - pass + with closed_tempfile(suffix='.' + extension) as fname: + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5) + anim.save(Path(fname), fps=30, writer=writer, bitrate=500) @cleanup diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 5fd2fb4e9f90..4274493edeff 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -14,7 +14,7 @@ from matplotlib import pyplot as plt from matplotlib.testing.decorators import (image_comparison, knownfailureif, cleanup) -from matplotlib.testing import skip +from matplotlib.testing import skip, closed_tempfile if 'TRAVIS' not in os.environ: @image_comparison(baseline_images=['pdf_use14corefonts'], @@ -57,14 +57,12 @@ def test_multipage_pagecount(): @cleanup def test_multipage_keep_empty(): from matplotlib.backends.backend_pdf import PdfPages - from tempfile import NamedTemporaryFile # test empty pdf files # test that an empty pdf is left behind with keep_empty=True (default) - with NamedTemporaryFile(delete=False) as tmp: + with closed_tempfile(".pdf") as tmp: with PdfPages(tmp) as pdf: filename = pdf._file.fh.name assert os.path.exists(filename) - os.remove(filename) # test if an empty pdf is deleting itself afterwards with keep_empty=False with PdfPages(filename, keep_empty=False) as pdf: pass @@ -74,19 +72,17 @@ def test_multipage_keep_empty(): ax = fig.add_subplot(111) ax.plot([1, 2, 3]) # test that a non-empty pdf is left behind with keep_empty=True (default) - with NamedTemporaryFile(delete=False) as tmp: + with closed_tempfile(".pdf") as tmp: with PdfPages(tmp) as pdf: filename = pdf._file.fh.name pdf.savefig() assert os.path.exists(filename) - os.remove(filename) # test that a non-empty pdf is left behind with keep_empty=False - with NamedTemporaryFile(delete=False) as tmp: + with closed_tempfile(".pdf") as tmp: with PdfPages(tmp, keep_empty=False) as pdf: filename = pdf._file.fh.name pdf.savefig() assert os.path.exists(filename) - os.remove(filename) @cleanup @@ -137,20 +133,17 @@ def test_grayscale_alpha(): @cleanup def test_pdfpages_accept_pep_519(): - from tempfile import NamedTemporaryFile - class FakeFSPathClass(object): def __init__(self, path): self._path = path def __fspath__(self): return self._path - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - with PdfPages(FakeFSPathClass(tmpfile.name)) as pdf: - fig, ax = plt.subplots() - ax.plot([1, 2], [3, 4]) - pdf.savefig(fig) + with closed_tempfile(suffix='.pdf') as fname: + with PdfPages(FakeFSPathClass(fname)) as pdf: + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + pdf.savefig(fig) @cleanup @@ -159,13 +152,11 @@ def test_savefig_accept_pathlib(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - with PdfPages(Path(tmpfile.name)) as pdf: - fig, ax = plt.subplots() - ax.plot([1, 2], [3, 4]) - pdf.savefig(fig) + with closed_tempfile(suffix='.pdf') as fname: + with PdfPages(Path(fname)) as pdf: + fig, ax = plt.subplots() + ax.plot([1, 2], [3, 4]) + pdf.savefig(fig) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 4fe48dd42b22..8830bea5818c 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -14,7 +14,7 @@ from nose.tools import (assert_equal, assert_not_equal, raises, assert_true, assert_raises) from matplotlib.testing.decorators import cleanup -from matplotlib.testing import skip +from matplotlib.testing import skip, closed_tempfile import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -529,8 +529,6 @@ def test_flatiter(): @cleanup def test_to_filehandle_accept_pep_519(): - from tempfile import NamedTemporaryFile - class FakeFSPathClass(object): def __init__(self, path): self._path = path @@ -538,10 +536,9 @@ def __init__(self, path): def __fspath__(self): return self._path - tmpfile = NamedTemporaryFile(delete=False) - tmpfile.close() - pep519_path = FakeFSPathClass(tmpfile.name) - cbook.to_filehandle(pep519_path) + with closed_tempfile() as tmpfile: + pep519_path = FakeFSPathClass(tmpfile) + cbook.to_filehandle(pep519_path) @cleanup @@ -550,9 +547,7 @@ def test_to_filehandle_accept_pathlib(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile - tmpfile = NamedTemporaryFile(delete=False) - tmpfile.close() - path = Path(tmpfile.name) - cbook.to_filehandle(path) + with closed_tempfile() as tmpfile: + path = Path(tmpfile) + cbook.to_filehandle(path) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index e33d77e1f184..9d8d9a74e332 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -8,7 +8,7 @@ from matplotlib import rcParams from matplotlib.testing.decorators import image_comparison, cleanup -from matplotlib.testing import skip +from matplotlib.testing import skip, closed_tempfile from matplotlib.axes import Axes import matplotlib.pyplot as plt import numpy as np @@ -209,8 +209,6 @@ def test_figaspect(): @cleanup def test_savefig_accept_pep_519_png(): - from tempfile import NamedTemporaryFile - class FakeFSPathClass(object): def __init__(self, path): self._path = path @@ -221,10 +219,9 @@ def __fspath__(self): fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.png') - tmpfile.close() - pep519_path = FakeFSPathClass(tmpfile.name) - fig.savefig(pep519_path) + with closed_tempfile(suffix='.png') as fname: + pep519_path = FakeFSPathClass(fname) + fig.savefig(pep519_path) @cleanup @@ -233,19 +230,16 @@ def test_savefig_accept_pathlib_png(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.png') - tmpfile.close() - path = Path(tmpfile.name) - fig.savefig(path) + with closed_tempfile(suffix='.png') as fname: + path = Path(fname) + fig.savefig(path) @cleanup def test_savefig_accept_pep_519_svg(): - from tempfile import NamedTemporaryFile class FakeFSPathClass(object): def __init__(self, path): @@ -256,10 +250,9 @@ def __fspath__(self): fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.svg') - tmpfile.close() - pep519_path = FakeFSPathClass(tmpfile.name) - fig.savefig(pep519_path) + with closed_tempfile(suffix='.svg') as fname: + pep519_path = FakeFSPathClass(fname) + fig.savefig(pep519_path) @cleanup @@ -268,19 +261,16 @@ def test_savefig_accept_pathlib_svg(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.svg') - tmpfile.close() - path = Path(tmpfile.name) - fig.savefig(path) + with closed_tempfile(suffix='.svg') as fname: + path = Path(fname) + fig.savefig(path) @cleanup def test_savefig_accept_pep_519_pdf(): - from tempfile import NamedTemporaryFile class FakeFSPathClass(object): def __init__(self, path): @@ -291,10 +281,9 @@ def __fspath__(self): fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - pep519_path = FakeFSPathClass(tmpfile.name) - fig.savefig(pep519_path) + with closed_tempfile(suffix='.pdf') as fname: + pep519_path = FakeFSPathClass(fname) + fig.savefig(pep519_path) @cleanup @@ -303,14 +292,12 @@ def test_savefig_accept_pathlib_pdf(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile fig, ax = plt.subplots() ax.plot([1, 2], [3, 4]) - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - path = Path(tmpfile.name) - fig.savefig(path) + with closed_tempfile(suffix='.pdf') as fname: + path = Path(fname) + fig.savefig(path) if __name__ == "__main__": import nose diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 51e529a4a341..de81fa1cf65d 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -12,6 +12,7 @@ from matplotlib.font_manager import ( findfont, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, fontManager as fm) +from matplotlib.testing import closed_tempfile import os.path @@ -36,15 +37,9 @@ def test_font_priority(): def test_json_serialization(): # on windows, we can't open a file twice, so save the name and unlink # manually... - try: - name = None - with tempfile.NamedTemporaryFile(delete=False) as temp: - name = temp.name - json_dump(fontManager, name) - copy = json_load(name) - finally: - if name and os.path.exists(name): - os.remove(name) + with closed_tempfile(".json") as temp: + json_dump(fontManager, temp) + copy = json_load(temp) with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'findfont: Font family.*not found') for prop in ({'family': 'STIXGeneral'}, diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index e3690e1497f3..670da66599d7 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -9,7 +9,7 @@ import numpy as np -from matplotlib.testing import skip +from matplotlib.testing import skip, closed_tempfile from matplotlib.testing.decorators import (image_comparison, knownfailureif, cleanup) from matplotlib.image import (BboxImage, imread, NonUniformImage, @@ -755,8 +755,6 @@ def test_imshow_endianess(): @cleanup def test_imsave_accept_pep_519(): - from tempfile import NamedTemporaryFile - class FakeFSPathClass(object): def __init__(self, path): self._path = path @@ -765,10 +763,9 @@ def __fspath__(self): return self._path a = np.array([[1, 2], [3, 4]]) - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - pep519_path = FakeFSPathClass(tmpfile.name) - plt.imsave(pep519_path, a) + with closed_tempfile(suffix='.pdf') as fname: + pep519_path = FakeFSPathClass(fname) + plt.imsave(pep519_path, a) @cleanup @@ -777,13 +774,11 @@ def test_imsave_accept_pathlib(): from pathlib import Path except ImportError: skip("pathlib not installed") - from tempfile import NamedTemporaryFile a = np.array([[1, 2], [3, 4]]) - tmpfile = NamedTemporaryFile(suffix='.pdf') - tmpfile.close() - path = Path(tmpfile.name) - plt.imsave(path, a) + with closed_tempfile(suffix='.pdf') as fname: + path = Path(fname) + plt.imsave(path, a) if __name__=='__main__': diff --git a/lib/matplotlib/tests/test_testing.py b/lib/matplotlib/tests/test_testing.py new file mode 100644 index 000000000000..17d37fd6ba56 --- /dev/null +++ b/lib/matplotlib/tests/test_testing.py @@ -0,0 +1,21 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import os.path + +from matplotlib.testing import closed_tempfile + + +def test_closed_tempfile(): + with closed_tempfile(".txt") as fname: + assert os.path.exists(fname) + assert fname.endswith(".txt") + name = fname + assert os.path.exists(name) + + +def test_closed_tempfile_text(): + text = "This is a test" + with closed_tempfile(".txt", text=text) as f: + with open(f, "rt") as g: + assert text == g.read()