Skip to content

Commit e993a7f

Browse files
committed
enh(filemanip): Solidify a patched version of pathlib.Path for internal consumption.
1 parent b76860d commit e993a7f

File tree

4 files changed

+67
-33
lines changed

4 files changed

+67
-33
lines changed

nipype/interfaces/base/specs.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
from traits.trait_errors import TraitError
2323
from traits.trait_handlers import TraitDictObject, TraitListObject
24-
from ...utils.filemanip import md5, hash_infile, hash_timestamp, to_str
24+
from ...utils.filemanip import (
25+
md5, hash_infile, hash_timestamp, to_str, USING_PATHLIB2)
2526
from .traits_extension import (
2627
traits,
2728
Undefined,
@@ -32,13 +33,6 @@
3233
from ... import config, __version__
3334

3435

35-
USING_PATHLIB2 = False
36-
try:
37-
from pathlib import Path
38-
except ImportError:
39-
from pathlib2 import Path # noqa
40-
USING_PATHLIB2 = True
41-
4236
FLOAT_FORMAT = '{:.10f}'.format
4337
nipype_version = Version(__version__)
4438

nipype/interfaces/base/traits_extension.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@
3434
from traits.api import Unicode
3535
from future import standard_library
3636

37-
try:
38-
from pathlib import Path
39-
except ImportError:
40-
from pathlib2 import Path
37+
from ...utils.filemanip import Path
4138

4239

4340
if traits_version < '3.7.0':
@@ -145,35 +142,18 @@ def validate(self, objekt, name, value, return_pathlike=False):
145142
self.error(objekt, name, str(value))
146143

147144
if self.resolve:
148-
try:
149-
value = value.resolve(strict=self.exists)
150-
except TypeError:
151-
if self.exists:
152-
value = value.resolve()
153-
elif not value.is_absolute():
154-
value = Path().resolve() / value
145+
value = value.resolve(strict=self.exists)
155146

156147
if not return_pathlike and not self.pathlike:
157148
value = str(value)
158149

159150
return value
160151

161-
# def get_value(self, objekt, name, trait=None):
162-
# value = super(BasePath, self).get_value(objekt, name)
163-
# if value is Undefined:
164-
# return self.default_value
165-
166-
# if self.pathlike:
167-
# return value
168-
# return str(value)
169-
170152

171153
class Directory(BasePath):
172154
"""
173155
Defines a trait whose value must be a directory path.
174156
175-
Examples::
176-
177157
>>> from nipype.interfaces.base import Directory, TraitedSpec, TraitError
178158
>>> class A(TraitedSpec):
179159
... foo = Directory(exists=False)

nipype/utils/filemanip.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import contextlib
2222
import posixpath
2323
import simplejson as json
24-
import numpy as np
2524

2625
from builtins import str, bytes, open
2726

@@ -40,8 +39,52 @@
4039

4140
PY3 = sys.version_info[0] >= 3
4241

43-
class FileNotFoundError(Exception):
42+
43+
class FileNotFoundError(OSError):
44+
"""Defines the expception for Python 2."""
45+
46+
def __init__(self, path):
47+
"""Initialize the exception."""
48+
super(FileNotFoundError, self).__init__(
49+
2, 'No such file or directory', '%s' % path)
50+
51+
52+
USING_PATHLIB2 = False
53+
USING_PATHLIB_PATCHED = False
54+
try:
55+
from pathlib import Path
56+
except ImportError:
57+
from pathlib2 import Path
58+
USING_PATHLIB2 = True
59+
60+
try:
61+
Path('/invented/file/path').resolve(strict=True)
62+
except TypeError:
63+
def _patch_resolve(self, strict=False):
64+
"""Add the argument strict to signature in Python>3,<3.6."""
65+
resolved = Path().old_resolve() / self
66+
67+
if strict and not resolved.exists():
68+
raise FileNotFoundError(resolved)
69+
return resolved
70+
71+
Path.old_resolve = Path.resolve
72+
Path.resolve = _patch_resolve
73+
USING_PATHLIB_PATCHED = True
74+
except FileNotFoundError:
4475
pass
76+
except OSError:
77+
def _patch_resolve(self, strict=False):
78+
"""Add the argument strict to signature for pathlib2."""
79+
try:
80+
resolved = self.old_resolve(strict=strict)
81+
except OSError:
82+
raise FileNotFoundError(self.old_resolve())
83+
84+
return resolved
85+
86+
Path.old_resolve = Path.resolve
87+
Path.resolve = _patch_resolve
4588

4689

4790
def split_filename(fname):

nipype/utils/tests/test_filemanip.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
check_forhash, _parse_mount_table, _cifs_table, on_cifs, copyfile,
1717
copyfiles, ensure_list, simplify_list, check_depends,
1818
split_filename, get_related_files, indirectory,
19-
loadpkl, loadcrash, savepkl)
19+
loadpkl, loadcrash, savepkl, FileNotFoundError, Path)
2020

2121

2222
def _ignore_atime(stat):
@@ -570,3 +570,20 @@ def test_unversioned_pklization(tmpdir):
570570
with pytest.raises(Exception):
571571
with mock.patch('nipype.utils.tests.test_filemanip.Pickled', PickledBreaker):
572572
loadpkl('./pickled.pkz', versioning=True)
573+
574+
575+
def test_Path_strict_resolve(tmpdir):
576+
"""Check the monkeypatch to test strict resolution of Path."""
577+
tmpdir.chdir()
578+
579+
# Default strict=False should work out out of the box
580+
testfile = Path('somefile.txt')
581+
assert '%s/somefile.txt' % tmpdir == '%s' % testfile.resolve()
582+
583+
# Switching to strict=True must raise FileNotFoundError (also in Python2)
584+
with pytest.raises(FileNotFoundError):
585+
testfile.resolve(strict=True)
586+
587+
# If the file is created, it should not raise
588+
testfile.write_text('')
589+
assert '%s/somefile.txt' % tmpdir == '%s' % testfile.resolve(strict=True)

0 commit comments

Comments
 (0)