-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add PEP 519 support #6788
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
Add PEP 519 support #6788
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should consider making What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I can do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I'm not sure what can actually be moved from matplotlib.cbook to matplotlib.cbook.fixes, as the fspath stuff is the only compatibility things I can see, and there's already matplotlib.compat... |
||
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__) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is not pep8 compliant. Please break the line after the +. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PEP8 does not prescribe either way, but has a slight preference for the code as it's written. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So change or leave? |
||
|
||
def fspath_no_except(path): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be a private function. I also don't really like this name, but I am not coming up with anything better for now… There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I seem to recall something (Sphinx? NumPyDoc?) using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That'd be a much better name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan of |
||
try: | ||
return fspath(path) | ||
except TypeError: | ||
return path |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please rewrite this docstring with the numpydoc format? I find the docstring relatively unclear on what exactly the function does. If I understand correctly, it creates, closes and returns a path to a file and deletes it on exiting the context. It is also unclear where the file is created. A note on how it relates and differs from Also, this is publicly available - do we want this publicly available? (if not, then the docstring comment can be ignored.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does publicly available mean should be used outside of matplotlib, or is the recommended option for other parts of matplotlib? I don't see much use outside matplotlib, unless you were already using matplotlib's testing utils. |
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That parenthesis here is weird. I am surprised pep8 allows this. Is there anyway to break the line in another way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with tempfile.NamedTemporaryFile('w+t', suffix=suffix,
delete=False) as test_file: fits within 79 chars, and looks reasonable, I think. |
||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
||
|
@@ -107,6 +107,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'): | ||
|
@@ -128,20 +134,72 @@ 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) | ||
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 | ||
def check_save_animation_pep_519(writer, extension='mp4'): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would avoid referencing to peps in function name. It is unclear what it does. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about check_save_animation_PathLike? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am, again, surprised pep8 allows that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't for me; I don't know why this is passing either. |
||
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, | ||
|
||
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 | ||
def check_save_animation_pathlib(writer, extension='mp4'): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test and the previous one looks like they could be refactoring to loop other Path and fspath compatible object to minimize the code duplication. |
||
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 | ||
from pathlib import Path | ||
except ImportError: | ||
skip("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, | ||
|
||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the standard library be able to handle this on it's own?