Skip to content

Commit 052461d

Browse files
committed
ENH: Modify Directory and File traits to get along with pathlib
Closes #2959
1 parent e722d61 commit 052461d

File tree

8 files changed

+350
-290
lines changed

8 files changed

+350
-290
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/specs.py

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

22+
from traits.trait_errors import TraitError
23+
from traits.trait_handlers import TraitDictObject, TraitListObject
2224
from ...utils.filemanip import md5, hash_infile, hash_timestamp, to_str
2325
from .traits_extension import (
2426
traits,
2527
Undefined,
2628
isdefined,
27-
TraitError,
28-
TraitDictObject,
29-
TraitListObject,
3029
has_metadata,
3130
)
3231

3332
from ... import config, __version__
3433

34+
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+
3542
FLOAT_FORMAT = '{:.10f}'.format
3643
nipype_version = Version(__version__)
3744

@@ -314,6 +321,39 @@ def __all__(self):
314321
return self.copyable_trait_names()
315322

316323

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

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
376+
377+
if not USING_PATHLIB2:
378+
DynamicTraitedSpec.__deepcopy__ = _deepcopypatch
359379

360380

361381
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)