Skip to content

Commit b1eb3ee

Browse files
authored
Merge pull request #2962 from oesteban/enh/improved-File-Directory-traits
ENH: Modify ``Directory`` and ``File`` traits to get along with pathlib
2 parents bab16a5 + d42db56 commit b1eb3ee

File tree

12 files changed

+394
-295
lines changed

12 files changed

+394
-295
lines changed

nipype/info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def get_nipype_gitversion():
155155
'packaging',
156156
'futures; python_version == "2.7"',
157157
'configparser; python_version <= "3.4"',
158+
'pathlib2; python_version <= "3.4"',
158159
]
159160

160161
TESTS_REQUIRES = [

nipype/interfaces/base/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
This module defines the API of all nipype interfaces.
99
1010
"""
11+
from traits.trait_handlers import TraitDictObject, TraitListObject
12+
from traits.trait_errors import TraitError
13+
1114
from .core import (Interface, BaseInterface, SimpleInterface, CommandLine,
1215
StdOutCommandLine, MpiCommandLine, SEMLikeCommandLine,
1316
LibraryBaseInterface, PackageInfo)
@@ -17,7 +20,7 @@
1720
StdOutCommandLineInputSpec)
1821

1922
from .traits_extension import (
20-
traits, Undefined, TraitDictObject, TraitListObject, TraitError, isdefined,
23+
traits, Undefined, isdefined,
2124
File, Directory, Str, DictStrStr, has_metadata, ImageFile,
2225
OutputMultiObject, InputMultiObject,
2326
OutputMultiPath, InputMultiPath)

nipype/interfaces/base/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import simplejson as json
2828
from dateutil.parser import parse as parseutc
2929
from future import standard_library
30+
from traits.trait_errors import TraitError
3031

3132
from ... import config, logging, LooseVersion
3233
from ...utils.provenance import write_provenance
@@ -37,7 +38,7 @@
3738

3839
from ...external.due import due
3940

40-
from .traits_extension import traits, isdefined, TraitError
41+
from .traits_extension import traits, isdefined
4142
from .specs import (BaseInterfaceInputSpec, CommandLineInputSpec,
4243
StdOutCommandLineInputSpec, MpiCommandLineInputSpec,
4344
get_filecopy_info)

nipype/interfaces/base/specs.py

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@
1919
from builtins import str, bytes
2020
from packaging.version import Version
2121

22-
from ...utils.filemanip import md5, hash_infile, hash_timestamp, to_str
22+
from traits.trait_errors import TraitError
23+
from traits.trait_handlers import TraitDictObject, TraitListObject
24+
from ...utils.filemanip import (
25+
md5, hash_infile, hash_timestamp, to_str, USING_PATHLIB2)
2326
from .traits_extension import (
2427
traits,
2528
Undefined,
2629
isdefined,
27-
TraitError,
28-
TraitDictObject,
29-
TraitListObject,
3030
has_metadata,
3131
)
3232

3333
from ... import config, __version__
3434

35+
3536
FLOAT_FORMAT = '{:.10f}'.format
3637
nipype_version = Version(__version__)
3738

@@ -314,6 +315,39 @@ def __all__(self):
314315
return self.copyable_trait_names()
315316

316317

318+
def _deepcopypatch(self, memo):
319+
"""
320+
Replace the ``__deepcopy__`` member with a traits-friendly implementation.
321+
322+
A bug in ``__deepcopy__`` for ``HasTraits`` results in weird cloning behaviors.
323+
Occurs for all specs in Python<3 and only for DynamicTraitedSpec in Python>2.
324+
325+
"""
326+
id_self = id(self)
327+
if id_self in memo:
328+
return memo[id_self]
329+
dup_dict = deepcopy(self.trait_get(), memo)
330+
# access all keys
331+
for key in self.copyable_trait_names():
332+
if key in self.__dict__.keys():
333+
_ = getattr(self, key)
334+
# clone once
335+
dup = self.clone_traits(memo=memo)
336+
for key in self.copyable_trait_names():
337+
try:
338+
_ = getattr(dup, key)
339+
except:
340+
pass
341+
# clone twice
342+
dup = self.clone_traits(memo=memo)
343+
dup.trait_set(**dup_dict)
344+
return dup
345+
346+
347+
if USING_PATHLIB2:
348+
BaseTraitedSpec.__deepcopy__ = _deepcopypatch
349+
350+
317351
class TraitedSpec(BaseTraitedSpec):
318352
""" Create a subclass with strict traits.
319353
@@ -333,29 +367,9 @@ class DynamicTraitedSpec(BaseTraitedSpec):
333367
functioning well together.
334368
"""
335369

336-
def __deepcopy__(self, memo):
337-
""" bug in deepcopy for HasTraits results in weird cloning behavior for
338-
added traits
339-
"""
340-
id_self = id(self)
341-
if id_self in memo:
342-
return memo[id_self]
343-
dup_dict = deepcopy(self.trait_get(), memo)
344-
# access all keys
345-
for key in self.copyable_trait_names():
346-
if key in self.__dict__.keys():
347-
_ = getattr(self, key)
348-
# clone once
349-
dup = self.clone_traits(memo=memo)
350-
for key in self.copyable_trait_names():
351-
try:
352-
_ = getattr(dup, key)
353-
except:
354-
pass
355-
# clone twice
356-
dup = self.clone_traits(memo=memo)
357-
dup.trait_set(**dup_dict)
358-
return dup
370+
371+
if not USING_PATHLIB2:
372+
DynamicTraitedSpec.__deepcopy__ = _deepcopypatch
359373

360374

361375
class CommandLineInputSpec(BaseInterfaceInputSpec):

nipype/interfaces/base/support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def _inputs_help(cls):
278278
279279
>>> from nipype.interfaces.afni import GCOR
280280
>>> _inputs_help(GCOR) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
281-
['Inputs::', '', '\t[Mandatory]', '\tin_file: (an existing file name)', ...
281+
['Inputs::', '', '\t[Mandatory]', '\tin_file: (a pathlike object or string...
282282
283283
"""
284284
helpstr = ['Inputs::']

nipype/interfaces/base/tests/test_specs.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
33
# vi: set ft=python sts=4 ts=4 sw=4 et:
44
from __future__ import print_function, unicode_literals
5-
from future import standard_library
65
import os
76
import warnings
7+
from future import standard_library
88

99
import pytest
1010

@@ -420,18 +420,17 @@ def test_ImageFile():
420420
# setup traits
421421
x.add_trait('nifti', nib.ImageFile(types=['nifti1', 'dicom']))
422422
x.add_trait('anytype', nib.ImageFile())
423-
x.add_trait('newtype', nib.ImageFile(types=['nifti10']))
423+
with pytest.raises(ValueError):
424+
x.add_trait('newtype', nib.ImageFile(types=['nifti10']))
424425
x.add_trait('nocompress',
425426
nib.ImageFile(types=['mgh'], allow_compressed=False))
426427

427428
with pytest.raises(nib.TraitError):
428429
x.nifti = 'test.mgz'
429430
x.nifti = 'test.nii'
430431
x.anytype = 'test.xml'
431-
with pytest.raises(AttributeError):
432-
x.newtype = 'test.nii'
433432
with pytest.raises(nib.TraitError):
434-
x.nocompress = 'test.nii.gz'
433+
x.nocompress = 'test.mgz'
435434
x.nocompress = 'test.mgh'
436435

437436

0 commit comments

Comments
 (0)