From 180fd5095d02e4a312efaf67c3a402ea3064f6f8 Mon Sep 17 00:00:00 2001 From: Cameron Riddell <31414128+CRiddler@users.noreply.github.com> Date: Thu, 15 Mar 2018 12:00:13 -0400 Subject: [PATCH 01/15] load save with pathlib.Path objects For python >= 3.6, objects representing paths now have the option to have a dunder `__fspath__` (See PEP 519 for more info) to return the string (or bytes) representation of the path. Pathlib on 3.6 includes this method so we should check for it first. Then, for pathlib objects from the py 3.4-3.5x, `__fspath__` is not available, so we dip into the py3k module to get the current python version's (2 or 3) string method (`unicode` in this case). So overall we're allowing filename to be a path object, but ensuring that it will be converted to the string/bytes representation of the path before passing it on to `image_klass.from_filename` My only reservations is that `nib.load` lowest entry point for this type of handling, because if some one really wants to call `from_filename` from an image_klass directly, they won't have this pathlib compatibility- however I think that `nib.load` is a very common entry point, which justifies the placement. --- nibabel/loadsave.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index 1dc576498b..8eec269ee6 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -17,7 +17,7 @@ from .filebasedimages import ImageFileError from .imageclasses import all_image_classes from .arrayproxy import is_proxy -from .py3k import FileNotFoundError +from .py3k import FileNotFoundError, unicode from .deprecated import deprecate_with_version @@ -36,6 +36,11 @@ def load(filename, **kwargs): img : ``SpatialImage`` Image of guessed type ''' + if hasattr(filename, '__fspath__'): + filename = filename.__fspath__() + else: + filename = unicode(filename) + if not op.exists(filename): raise FileNotFoundError("No such file: '%s'" % filename) sniff = None From 33466894059fd45d5e23373ac7e3034e35995673 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Mon, 16 Sep 2019 10:42:00 -0700 Subject: [PATCH 02/15] Add fn to coerce pathlike to string --- nibabel/loadsave.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index fabeb55c60..515ffdec34 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -11,6 +11,7 @@ import os import numpy as np +import pathlib from .filename_parser import splitext_addext from .openers import ImageOpener @@ -19,6 +20,33 @@ from .arrayproxy import is_proxy from .deprecated import deprecate_with_version +def _stringify_path(filepath_or_buffer): + """Attempt to convert a path-like object to a string. + + Parameters + ---------- + filepath_or_buffer : object to be converted + Returns + ------- + str_filepath_or_buffer : maybe a string version of the object + Notes + ----- + Objects supporting the fspath protocol (python 3.6+) are coerced + according to its __fspath__ method. + For backwards compatibility with older pythons, pathlib.Path and + py.path objects are specially coerced. + Any other object is passed through unchanged, which includes bytes, + strings, buffers, or anything else that's not even path-like. + + Copied from: + https://github.com/pandas-dev/pandas/blob/325dd686de1589c17731cf93b649ed5ccb5a99b4/pandas/io/common.py#L131-L160 + """ + if hasattr(filepath_or_buffer, "__fspath__"): + return filepath_or_buffer.__fspath__() + elif isinstance(filepath_or_buffer, pathlib.Path): + return str(filepath_or_buffer) + return filepath_or_buffer + def load(filename, **kwargs): ''' Load file given filename, guessing at file type @@ -35,11 +63,6 @@ def load(filename, **kwargs): img : ``SpatialImage`` Image of guessed type ''' - #Coerce pathlib objects - if hasattr(filename, '__fspath__'): - filename = filename.__fspath__() - else: - filename = str(filename) #Check file exists and is not empty try: From aaf1a198906037e8feeb4c44c24d7987ca4b8320 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Mon, 16 Sep 2019 10:54:27 -0700 Subject: [PATCH 03/15] Insert _stringify_path to entry points --- nibabel/filebasedimages.py | 4 ++++ nibabel/filename_parser.py | 5 +++++ nibabel/loadsave.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 64b79550e3..3d2877aa82 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -15,6 +15,7 @@ splitext_addext) from .openers import ImageOpener from .deprecated import deprecate_with_version +from .loadsave import _stringify_path class ImageFileError(Exception): @@ -252,10 +253,12 @@ def set_filename(self, filename): ``.file_map`` attribute. Otherwise, the image instance will try and guess the other filenames from this given filename. ''' + filename = _stringify_path(filename) self.file_map = self.__class__.filespec_to_file_map(filename) @classmethod def from_filename(klass, filename): + filename = _stringify_path(filename) file_map = klass.filespec_to_file_map(filename) return klass.from_file_map(file_map) @@ -330,6 +333,7 @@ def to_filename(self, filename): ------- None ''' + filename = _stringify_path(filename) self.file_map = self.filespec_to_file_map(filename) self.to_file_map() diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index db6e073018..8c9ed07892 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -14,6 +14,7 @@ except NameError: basestring = str +from .loadsave import _stringify_path class TypesFilenamesError(Exception): pass @@ -190,6 +191,8 @@ def parse_filename(filename, >>> parse_filename('/path/fnameext2.gz', types_exts, ('.gz',)) ('/path/fname', 'ext2', '.gz', 't2') ''' + filename = _stringify_path(filename) + ignored = None if match_case: endswith = _endswith @@ -257,6 +260,8 @@ def splitext_addext(filename, >>> splitext_addext('fname.ext.foo', ('.foo', '.bar')) ('fname', '.ext', '.foo') ''' + filename = _stringify_path(filename) + if match_case: endswith = _endswith else: diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index 515ffdec34..5e7d199545 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -63,6 +63,7 @@ def load(filename, **kwargs): img : ``SpatialImage`` Image of guessed type ''' + filename = _stringify_path(filename) #Check file exists and is not empty try: @@ -123,6 +124,7 @@ def save(img, filename): ------- None ''' + filename = _stringify_path(filename) # Save the type as expected try: From 185f82c69640195cb64eba9e73e93e07d8eedda8 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Tue, 17 Sep 2019 11:21:18 -0700 Subject: [PATCH 04/15] Refactor _stringify_path to filename_parser.py --- nibabel/filebasedimages.py | 3 +-- nibabel/filename_parser.py | 29 ++++++++++++++++++++++++++++- nibabel/loadsave.py | 28 +--------------------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 3d2877aa82..4f84e0c1b4 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -12,10 +12,9 @@ from copy import deepcopy from .fileholders import FileHolder from .filename_parser import (types_filenames, TypesFilenamesError, - splitext_addext) + splitext_addext, _stringify_path) from .openers import ImageOpener from .deprecated import deprecate_with_version -from .loadsave import _stringify_path class ImageFileError(Exception): diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index 8c9ed07892..8f69c4a843 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -14,11 +14,38 @@ except NameError: basestring = str -from .loadsave import _stringify_path +import pathlib class TypesFilenamesError(Exception): pass +def _stringify_path(filepath_or_buffer): + """Attempt to convert a path-like object to a string. + + Parameters + ---------- + filepath_or_buffer : object to be converted + Returns + ------- + str_filepath_or_buffer : maybe a string version of the object + Notes + ----- + Objects supporting the fspath protocol (python 3.6+) are coerced + according to its __fspath__ method. + For backwards compatibility with older pythons, pathlib.Path and + py.path objects are specially coerced. + Any other object is passed through unchanged, which includes bytes, + strings, buffers, or anything else that's not even path-like. + + Copied from: + https://github.com/pandas-dev/pandas/blob/325dd686de1589c17731cf93b649ed5ccb5a99b4/pandas/io/common.py#L131-L160 + """ + if hasattr(filepath_or_buffer, "__fspath__"): + return filepath_or_buffer.__fspath__() + elif isinstance(filepath_or_buffer, pathlib.Path): + return str(filepath_or_buffer) + return filepath_or_buffer + def types_filenames(template_fname, types_exts, trailing_suffixes=('.gz', '.bz2'), diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index 5e7d199545..015973914c 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -11,41 +11,15 @@ import os import numpy as np -import pathlib -from .filename_parser import splitext_addext +from .filename_parser import splitext_addext, _stringify_path from .openers import ImageOpener from .filebasedimages import ImageFileError from .imageclasses import all_image_classes from .arrayproxy import is_proxy from .deprecated import deprecate_with_version -def _stringify_path(filepath_or_buffer): - """Attempt to convert a path-like object to a string. - Parameters - ---------- - filepath_or_buffer : object to be converted - Returns - ------- - str_filepath_or_buffer : maybe a string version of the object - Notes - ----- - Objects supporting the fspath protocol (python 3.6+) are coerced - according to its __fspath__ method. - For backwards compatibility with older pythons, pathlib.Path and - py.path objects are specially coerced. - Any other object is passed through unchanged, which includes bytes, - strings, buffers, or anything else that's not even path-like. - - Copied from: - https://github.com/pandas-dev/pandas/blob/325dd686de1589c17731cf93b649ed5ccb5a99b4/pandas/io/common.py#L131-L160 - """ - if hasattr(filepath_or_buffer, "__fspath__"): - return filepath_or_buffer.__fspath__() - elif isinstance(filepath_or_buffer, pathlib.Path): - return str(filepath_or_buffer) - return filepath_or_buffer def load(filename, **kwargs): From e1d78c8e4c5eed24fabab4e9d925985abb695b0b Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Mon, 23 Sep 2019 12:30:59 -0700 Subject: [PATCH 05/15] resolve flake8 --- nibabel/filename_parser.py | 6 ++++-- nibabel/loadsave.py | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index 8f69c4a843..f6e23e2dce 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -16,9 +16,11 @@ import pathlib + class TypesFilenamesError(Exception): pass + def _stringify_path(filepath_or_buffer): """Attempt to convert a path-like object to a string. @@ -41,7 +43,7 @@ def _stringify_path(filepath_or_buffer): https://github.com/pandas-dev/pandas/blob/325dd686de1589c17731cf93b649ed5ccb5a99b4/pandas/io/common.py#L131-L160 """ if hasattr(filepath_or_buffer, "__fspath__"): - return filepath_or_buffer.__fspath__() + return filepath_or_buffer.__fspath__() elif isinstance(filepath_or_buffer, pathlib.Path): return str(filepath_or_buffer) return filepath_or_buffer @@ -288,7 +290,7 @@ def splitext_addext(filename, ('fname', '.ext', '.foo') ''' filename = _stringify_path(filename) - + if match_case: endswith = _endswith else: diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index 015973914c..ecb6b6cb18 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -20,8 +20,6 @@ from .deprecated import deprecate_with_version - - def load(filename, **kwargs): ''' Load file given filename, guessing at file type @@ -39,14 +37,14 @@ def load(filename, **kwargs): ''' filename = _stringify_path(filename) - #Check file exists and is not empty + # Check file exists and is not empty try: stat_result = os.stat(filename) except OSError: raise FileNotFoundError("No such file or no access: '%s'" % filename) if stat_result.st_size <= 0: raise ImageFileError("Empty file: '%s'" % filename) - + sniff = None for image_klass in all_image_classes: is_valid, sniff = image_klass.path_maybe_image(filename, sniff) From d9ce63e0cc51dbd87d333a7cfe52c700ccdfbfa2 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Mon, 14 Oct 2019 16:31:46 -0700 Subject: [PATCH 06/15] testing for pathlib --- nibabel/tests/test_image_api.py | 25 ++++++++++++++----------- nibabel/tests/test_image_load_save.py | 16 +++++++++------- nibabel/tests/test_loadsave.py | 18 +++++++++++------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 979b8777f9..0650feda84 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -27,6 +27,7 @@ import warnings from functools import partial from itertools import product +import pathlib import numpy as np @@ -144,19 +145,21 @@ def validate_filenames(self, imaker, params): assert_almost_equal(img.get_data(), rt_rt_img.get_data()) # get_ / set_ filename fname = 'an_image' + self.standard_extension - img.set_filename(fname) - assert_equal(img.get_filename(), fname) - assert_equal(img.file_map['image'].filename, fname) + for path in (fname, pathlib.Path(fname)): + img.set_filename(path) + assert_equal(img.get_filename(), path) + assert_equal(img.file_map['image'].filename, path) # to_ / from_ filename fname = 'another_image' + self.standard_extension - with InTemporaryDirectory(): - img.to_filename(fname) - rt_img = img.__class__.from_filename(fname) - assert_array_equal(img.shape, rt_img.shape) - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - # get_data will be deprecated - assert_almost_equal(img.get_data(), rt_img.get_data()) - del rt_img # to allow windows to delete the directory + for path in (fname, pathlib.Path(fname)): + with InTemporaryDirectory(): + img.to_filename(path) + rt_img = img.__class__.from_filename(path) + assert_array_equal(img.shape, rt_img.shape) + assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) + # get_data will be deprecated + assert_almost_equal(img.get_data(), rt_img.get_data()) + del rt_img # to allow windows to delete the directory def validate_no_slicing(self, imaker, params): img = imaker() diff --git a/nibabel/tests/test_image_load_save.py b/nibabel/tests/test_image_load_save.py index 7101b6a31b..ce3db18113 100644 --- a/nibabel/tests/test_image_load_save.py +++ b/nibabel/tests/test_image_load_save.py @@ -12,6 +12,7 @@ import shutil from os.path import dirname, join as pjoin from tempfile import mkdtemp +import pathlib import numpy as np @@ -253,13 +254,14 @@ def test_filename_save(): try: pth = mkdtemp() fname = pjoin(pth, 'image' + out_ext) - nils.save(img, fname) - rt_img = nils.load(fname) - assert_array_almost_equal(rt_img.get_data(), data) - assert_true(type(rt_img) is loadklass) - # delete image to allow file close. Otherwise windows - # raises an error when trying to delete the directory - del rt_img + for path in (fname, pathlib.Path(fname)): + nils.save(img, path) + rt_img = nils.load(path) + assert_array_almost_equal(rt_img.get_data(), data) + assert_true(type(rt_img) is loadklass) + # delete image to allow file close. Otherwise windows + # raises an error when trying to delete the directory + del rt_img finally: shutil.rmtree(pth) diff --git a/nibabel/tests/test_loadsave.py b/nibabel/tests/test_loadsave.py index 4c1c703389..fceee6813e 100644 --- a/nibabel/tests/test_loadsave.py +++ b/nibabel/tests/test_loadsave.py @@ -3,6 +3,7 @@ from os.path import dirname, join as pjoin import shutil +import pathlib import numpy as np @@ -26,13 +27,16 @@ def test_read_img_data(): - for fname in ('example4d.nii.gz', - 'example_nifti2.nii.gz', - 'minc1_1_scale.mnc', - 'minc1_4d.mnc', - 'test.mgz', - 'tiny.mnc' - ): + fnames_test = [ + 'example4d.nii.gz', + 'example_nifti2.nii.gz', + 'minc1_1_scale.mnc', + 'minc1_4d.mnc', + 'test.mgz', + 'tiny.mnc' + ] + fnames_test += [pathlib.Path(p) for p in fnames_test] + for fname in fnames_test: fpath = pjoin(data_path, fname) img = load(fpath) data = img.get_data() From 7887232c521f5cc77f09399db7eb5ea06c2b5b7e Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Mon, 14 Oct 2019 16:37:35 -0700 Subject: [PATCH 07/15] purge basestring --- nibabel/filename_parser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index f6e23e2dce..553abd8fa7 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -9,11 +9,6 @@ ''' Create filename pairs, triplets etc, with expected extensions ''' import os -try: - basestring -except NameError: - basestring = str - import pathlib @@ -112,7 +107,7 @@ def types_filenames(template_fname, types_exts, >>> tfns == {'t1': '/path/test.funny', 't2': '/path/test.ext2'} True ''' - if not isinstance(template_fname, basestring): + if not isinstance(template_fname, str): raise TypesFilenamesError('Need file name as input ' 'to set_filenames') if template_fname.endswith('.'): From 6c0497ee8357fdf9ef078cd0b4c0d5a011e82496 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Tue, 15 Oct 2019 17:51:00 -0700 Subject: [PATCH 08/15] move stringify guard to types_filenames --- nibabel/filebasedimages.py | 3 --- nibabel/filename_parser.py | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 4f84e0c1b4..d8af14a180 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -252,12 +252,10 @@ def set_filename(self, filename): ``.file_map`` attribute. Otherwise, the image instance will try and guess the other filenames from this given filename. ''' - filename = _stringify_path(filename) self.file_map = self.__class__.filespec_to_file_map(filename) @classmethod def from_filename(klass, filename): - filename = _stringify_path(filename) file_map = klass.filespec_to_file_map(filename) return klass.from_file_map(file_map) @@ -332,7 +330,6 @@ def to_filename(self, filename): ------- None ''' - filename = _stringify_path(filename) self.file_map = self.filespec_to_file_map(filename) self.to_file_map() diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index 553abd8fa7..e4e69cc843 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -107,6 +107,7 @@ def types_filenames(template_fname, types_exts, >>> tfns == {'t1': '/path/test.funny', 't2': '/path/test.ext2'} True ''' + template_fname = _stringify_path(template_fname) if not isinstance(template_fname, str): raise TypesFilenamesError('Need file name as input ' 'to set_filenames') From e0fff5c1edec58b60bb12b539416c26a08d61060 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Tue, 15 Oct 2019 17:56:18 -0700 Subject: [PATCH 09/15] update docstrings to accept str or os.PathLike --- nibabel/filebasedimages.py | 10 +++++----- nibabel/filename_parser.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index d8af14a180..3566af3e3d 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -246,7 +246,7 @@ def set_filename(self, filename): Parameters ---------- - filename : str + filename : str or os.PathLike If the image format only has one file associated with it, this will be the only filename set into the image ``.file_map`` attribute. Otherwise, the image instance will @@ -279,7 +279,7 @@ def filespec_to_file_map(klass, filespec): Parameters ---------- - filespec : str + filespec : str or os.PathLike Filename that might be for this image file type. Returns @@ -321,7 +321,7 @@ def to_filename(self, filename): Parameters ---------- - filename : str + filename : str or os.PathLike filename to which to save image. We will parse `filename` with ``filespec_to_file_map`` to work out names for image, header etc. @@ -419,7 +419,7 @@ def _sniff_meta_for(klass, filename, sniff_nbytes, sniff=None): Parameters ---------- - filename : str + filename : str or os.PathLike Filename for an image, or an image header (metadata) file. If `filename` points to an image data file, and the image type has a separate "header" file, we work out the name of the header file, @@ -466,7 +466,7 @@ def path_maybe_image(klass, filename, sniff=None, sniff_max=1024): Parameters ---------- - filename : str + filename : str or os.PathLike Filename for an image, or an image header (metadata) file. If `filename` points to an image data file, and the image type has a separate "header" file, we work out the name of the header file, diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index e4e69cc843..484dd9f011 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -21,7 +21,7 @@ def _stringify_path(filepath_or_buffer): Parameters ---------- - filepath_or_buffer : object to be converted + filepath_or_buffer : str or os.PathLike Returns ------- str_filepath_or_buffer : maybe a string version of the object @@ -56,7 +56,7 @@ def types_filenames(template_fname, types_exts, Parameters ---------- - template_fname : str + template_fname : str or os.PathLike template filename from which to construct output dict of filenames, with given `types_exts` type to extension mapping. If ``self.enforce_extensions`` is True, then filename must have one @@ -177,7 +177,7 @@ def parse_filename(filename, Parameters ---------- - filename : str + filename : str or os.PathLike filename in which to search for type extensions types_exts : sequence of sequences sequence of (name, extension) str sequences defining type to @@ -260,7 +260,7 @@ def splitext_addext(filename, Parameters ---------- - filename : str + filename : str or os.PathLike filename that may end in any or none of `addexts` match_case : bool, optional If True, match case of `addexts` and `filename`, otherwise do From 6056014b6558913a9adc93c989f68734edab05a6 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 16 Oct 2019 13:33:19 -0700 Subject: [PATCH 10/15] update _stringify_path doc --- nibabel/filename_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index 484dd9f011..45de93aef9 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -22,6 +22,7 @@ def _stringify_path(filepath_or_buffer): Parameters ---------- filepath_or_buffer : str or os.PathLike + Returns ------- str_filepath_or_buffer : maybe a string version of the object From 0cb7c39ca1f8ec9b61dbd391eafdeee3cb826420 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 16 Oct 2019 12:31:54 -0700 Subject: [PATCH 11/15] update docstrings to accept str or os.PathLike --- nibabel/loadsave.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index 5d740bd178..f8c3e3be0b 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -25,7 +25,7 @@ def load(filename, **kwargs): Parameters ---------- - filename : string + filename : str or os.PathLike specification of file to load \*\*kwargs : keyword arguments Keyword arguments to format-specific load @@ -89,7 +89,7 @@ def save(img, filename): ---------- img : ``SpatialImage`` image to save - filename : str + filename : str or os.PathLike filename (often implying filenames) to which to save `img`. Returns From ca10c447b07c088ba649fb2250129638796bdb44 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 16 Oct 2019 12:35:03 -0700 Subject: [PATCH 12/15] mghformat accept pathlib for filespec_to_file_map --- nibabel/freesurfer/mghformat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nibabel/freesurfer/mghformat.py b/nibabel/freesurfer/mghformat.py index ddb30cb796..6eb0f156e9 100644 --- a/nibabel/freesurfer/mghformat.py +++ b/nibabel/freesurfer/mghformat.py @@ -17,6 +17,7 @@ from ..volumeutils import (array_to_file, array_from_file, endian_codes, Recoder) from ..filebasedimages import SerializableImage +from ..filename_parser import _stringify_path from ..spatialimages import HeaderDataError, SpatialImage from ..fileholders import FileHolder from ..arrayproxy import ArrayProxy, reshape_dataobj @@ -529,6 +530,7 @@ def __init__(self, dataobj, affine, header=None, @classmethod def filespec_to_file_map(klass, filespec): + filespec = _stringify_path(filespec) """ Check for compressed .mgz format, then .mgh format """ if splitext(filespec)[1].lower() == '.mgz': return dict(image=FileHolder(filename=filespec)) From e98dbfc09149a9a97405112a2101bfc2075a4ff8 Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 16 Oct 2019 13:06:54 -0700 Subject: [PATCH 13/15] tests pathlib compatible --- nibabel/tests/test_image_api.py | 4 ++-- nibabel/tests/test_image_load_save.py | 2 +- nibabel/tests/test_loadsave.py | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 27aa3abc36..3b921b9fb9 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -144,8 +144,8 @@ def validate_filenames(self, imaker, params): fname = 'an_image' + self.standard_extension for path in (fname, pathlib.Path(fname)): img.set_filename(path) - assert_equal(img.get_filename(), path) - assert_equal(img.file_map['image'].filename, path) + assert_equal(img.get_filename(), str(path)) + assert_equal(img.file_map['image'].filename, str(path)) # to_ / from_ filename fname = 'another_image' + self.standard_extension for path in (fname, pathlib.Path(fname)): diff --git a/nibabel/tests/test_image_load_save.py b/nibabel/tests/test_image_load_save.py index f60ea54ea0..9d58a3ed60 100644 --- a/nibabel/tests/test_image_load_save.py +++ b/nibabel/tests/test_image_load_save.py @@ -259,7 +259,7 @@ def test_filename_save(): for path in (fname, pathlib.Path(fname)): nils.save(img, path) rt_img = nils.load(path) - assert_array_almost_equal(rt_img.get_data(), data) + assert_array_almost_equal(rt_img.get_fdata(), data) assert_true(type(rt_img) is loadklass) # delete image to allow file close. Otherwise windows # raises an error when trying to delete the directory diff --git a/nibabel/tests/test_loadsave.py b/nibabel/tests/test_loadsave.py index 1925422590..3d7101b6d3 100644 --- a/nibabel/tests/test_loadsave.py +++ b/nibabel/tests/test_loadsave.py @@ -37,7 +37,10 @@ def test_read_img_data(): ] fnames_test += [pathlib.Path(p) for p in fnames_test] for fname in fnames_test: - fpath = pjoin(data_path, fname) + # os.path.join doesnt work between str / os.PathLike in py3.5 + fpath = pjoin(data_path, str(fname)) + if isinstance(fname, pathlib.Path): + fpath = pathlib.Path(fpath) img = load(fpath) data = img.get_fdata() data2 = read_img_data(img) @@ -49,8 +52,11 @@ def test_read_img_data(): assert_array_equal(read_img_data(img, prefer='unscaled'), data) # Assert all caps filename works as well with TemporaryDirectory() as tmpdir: - up_fpath = pjoin(tmpdir, fname.upper()) - shutil.copyfile(fpath, up_fpath) + up_fpath = pjoin(tmpdir, str(fname).upper()) + if isinstance(fname, pathlib.Path): + up_fpath = pathlib.Path(up_fpath) + # shutil doesnt work with os.PathLike in py3.5 + shutil.copyfile(str(fpath), str(up_fpath)) img = load(up_fpath) assert_array_equal(img.dataobj, data) del img From 63945366103eebacff77203d02fbfd3643a4645c Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 16 Oct 2019 15:29:53 -0700 Subject: [PATCH 14/15] update _stringify_path doc --- nibabel/filename_parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index 45de93aef9..b8a03060c0 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -25,13 +25,14 @@ def _stringify_path(filepath_or_buffer): Returns ------- - str_filepath_or_buffer : maybe a string version of the object + str_filepath_or_buffer : str + Notes ----- Objects supporting the fspath protocol (python 3.6+) are coerced according to its __fspath__ method. - For backwards compatibility with older pythons, pathlib.Path and - py.path objects are specially coerced. + For backwards compatibility with older pythons, pathlib.Path objects + are specially coerced. Any other object is passed through unchanged, which includes bytes, strings, buffers, or anything else that's not even path-like. From 60a46247c29c0e82fb38db0fa64381de8784c6dd Mon Sep 17 00:00:00 2001 From: Cameron Riddell Date: Wed, 23 Oct 2019 08:30:41 -0700 Subject: [PATCH 15/15] Fix flake 8 issues --- nibabel/filebasedimages.py | 2 +- nibabel/filename_parser.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 76e6c8bfa4..90bbd8e652 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -12,7 +12,7 @@ from copy import deepcopy from .fileholders import FileHolder from .filename_parser import (types_filenames, TypesFilenamesError, - splitext_addext, _stringify_path) + splitext_addext) from .openers import ImageOpener from .deprecated import deprecate_with_version diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py index b8a03060c0..ed04610fdd 100644 --- a/nibabel/filename_parser.py +++ b/nibabel/filename_parser.py @@ -26,12 +26,12 @@ def _stringify_path(filepath_or_buffer): Returns ------- str_filepath_or_buffer : str - + Notes ----- Objects supporting the fspath protocol (python 3.6+) are coerced according to its __fspath__ method. - For backwards compatibility with older pythons, pathlib.Path objects + For backwards compatibility with older pythons, pathlib.Path objects are specially coerced. Any other object is passed through unchanged, which includes bytes, strings, buffers, or anything else that's not even path-like.