8000 Add pep 519 by tacaswell · Pull Request #8481 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content
< 8000 div hidden="hidden" data-view-component="true" class="js-stale-session-flash stale-session-flash flash flash-warn flash-full"> Dismiss alert

Add pep 519 #8481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
import contextlib
import tempfile
import warnings
from matplotlib.cbook import iterable, is_string_like, deprecated
from matplotlib.cbook import (iterable, is_string_like, deprecated,
fspath_no_except)
from matplotlib.compat import subprocess
from matplotlib import verbose
from matplotlib import rcParams, rcParamsDefault, rc_context
Expand Down Expand Up @@ -349,6 +350,7 @@ def _run(self):
output = sys.stdout
else:
output = subprocess.PIPE
command = [fspath_no_except(cmd) for cmd in command]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably just be done on self.output

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even better to matplotlib.compat.{Popen,check_output}:

subprocess.Popen([Path("ls"), Path("-a"), Path(".")])

works (even though this is arguably silly...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by 'output', I think I meant 'outfile'..

verbose.report('MovieWriter.run: running command: %s' %
' '.join(command))
self._proc = subprocess.Popen(command, shell=False,
Expand Down
13 changes: 8 additions & 5 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
from matplotlib import verbose, rcParams, __version__
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,
Expand Down Expand Up @@ -563,8 +564,9 @@ def print_png(self, filename_or_obj, *args, **kwargs):
metadata.update(user_metadata)

try:
_png.write_png(renderer._renderer, filename_or_obj,
self.figure.dpi, metadata=metadata)
_png.write_png(
renderer._renderer, fspath_no_except(filename_or_obj),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the conversion should almost certainly occur higher, around line 554.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks wrong, I don't remember why I did this...

self.figure.dpi, metadata=metadata)
finally:
if close:
filename_or_obj.close()
Expand Down Expand Up @@ -620,7 +622,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
Expand All @@ -630,7 +633,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

Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,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
Expand Down Expand Up @@ -434,6 +434,7 @@ def __init__(self, filename, metadata=None):
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):
Expand Down
54 changes: 50 additions & 4 deletions lib/matplotlib/cbook/__init__.py
F438
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,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.
Expand Down Expand Up @@ -619,6 +620,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:
Expand Down Expand Up @@ -827,6 +829,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)
Expand Down Expand Up @@ -987,7 +990,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))
Expand All @@ -1011,10 +1014,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)

Expand Down Expand Up @@ -1543,6 +1546,7 @@ def simple_linear_interpolation(a, steps):

@deprecated('2.1', alternative='shutil.rmtree')
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, '.*'))):
Expand Down Expand Up @@ -2412,7 +2416,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 + '-*')
Expand Down Expand Up @@ -2703,3 +2707,45 @@ def _get_key_params(self):
(params, str_func))

return str_func, params


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
2 changes: 1 addition & 1 deletion lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ def write_png(self, fname):
"""Write the image to png file with fname"""
im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A,
bytes=True, norm=True)
_png.write_png(im, fname)
_png.write_png(im, cbook.fspath_no_except(fname))

def set_data(self, A):
"""
Expand Down
20 changes: 20 additions & 0 deletions lib/matplotlib/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -144,3 +146,21 @@ def setup():

set_font_settings_for_testing()
set_reproducibility_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)
81 changes: 57 additions & 24 deletions lib/matplotlib/tests/test_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@

import six

import os
import sys
import tempfile

import numpy as np
import pytest

import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import animation
from ..testing import closed_tempfile


class NullMovieWriter(animation.AbstractMovieWriter):
Expand Down Expand Up @@ -125,20 +124,7 @@ def isAvailable(self):
]


# Smoke test for saving animations. In the future, we should probably
# design more sophisticated tests which compare resulting frames a-la
# matplotlib.testing.image_comparison
@pytest.mark.parametrize('writer, extension', WRITER_OUTPUT)
def test_save_animation_smoketest(tmpdir, writer, extension):
try:
# for ImageMagick the rcparams must be patched to account for
# 'convert' being a built in MS tool, not the imagemagick
# tool.
writer._init_from_registry()
except AttributeError:
pass
if not animation.writers.is_available(writer):
pytest.skip("writer '%s' not available on this system" % writer)
def _inner_animation(writer):
fig, ax = plt.subplots()
line, = ax.plot([], [])

Expand All @@ -162,17 +148,64 @@ def animate(i):
y = np.sin(x + i)
line.set_data(x, y)
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5)
return fig, anim, dpi, codec


# Smoke test for saving animations. In the future, we should probably
# design more sophisticated tests which compare resulting frames a-la
# matplotlib.testing.image_comparison
@pytest.mark.parametrize('writer, extension', WRITER_OUTPUT)
def test_save_animation_smoketest(tmpdir, writer, extension):
try:
# for ImageMagick the rcparams must be patched to account for
# 'convert' being a built in MS tool, not the imagemagick
# tool.
writer._init_from_registry()
except AttributeError:
pass
if not animation.writers.is_available(writer):
pytest.skip("writer '%s' not available on this system" % writer)

fig, anim, dpi, codec = _inner_animation(writer)

# Use temporary directory for the file-based writers, which produce a file
# per frame with known names.
with tmpdir.as_cwd():
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5)
try:
anim.save('movie.' + extension, fps=30, writer=writer, bitrate=500,
dpi=dpi, codec=codec)
except UnicodeDecodeError:
pytest.xfail("There can be errors in the numpy import stack, "
"see issues #1891 and #2679")

with closed_tempfile(suffix='.' + extension) as fname:
anim.save(fname, fps=30, writer=writer, bitrate=500)


@pytest.mark.parametrize('writer, extension', WRITER_OUTPUT)
def test_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):
pytest.skip("writer '%s' not available on this system" % writer)

fig, anim, dpi, codec = _inner_animation(writer)
with closed_tempfile(suffix='.' + extension) as fname:
anim.save(FakeFSPathClass(fname), fps=30, writer=writer, bitrate=500,
dpi=dpi, codec=codec)


@pytest.mark.parametrize('writer, extension', WRITER_OUTPUT)
def test_save_animation_pathlib(writer, extension='mp4'):
try:
from pathlib import Path
except ImportError:
raise pytest.skip("pathlib not installed")
if not animation.writers.is_available(writer):
pytest.skip("writer '%s' not available on this system" % writer)

fig, anim, dpi, codec = _inner_animation(writer)
with closed_tempfile(suffix='.' + extension) as fname:
anim.save(Path(fname), fps=30, writer=writer, bitrate=500,
dpi=dpi, codec=codec)


def test_no_length_frames():
Expand Down
Loading
0