Skip to content

ENH: Modify Directory and File traits to get along with pathlib #2962

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

Merged
merged 11 commits into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nipype/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def get_nipype_gitversion():
'packaging',
'futures; python_version == "2.7"',
'configparser; python_version <= "3.4"',
'pathlib2; python_version <= "3.4"',
]

TESTS_REQUIRES = [
Expand Down
5 changes: 4 additions & 1 deletion nipype/interfaces/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
This module defines the API of all nipype interfaces.

"""
from traits.trait_handlers import TraitDictObject, TraitListObject
from traits.trait_errors import TraitError

from .core import (Interface, BaseInterface, SimpleInterface, CommandLine,
StdOutCommandLine, MpiCommandLine, SEMLikeCommandLine,
LibraryBaseInterface, PackageInfo)
Expand All @@ -17,7 +20,7 @@
StdOutCommandLineInputSpec)

from .traits_extension import (
traits, Undefined, TraitDictObject, TraitListObject, TraitError, isdefined,
traits, Undefined, isdefined,
File, Directory, Str, DictStrStr, has_metadata, ImageFile,
OutputMultiObject, InputMultiObject,
OutputMultiPath, InputMultiPath)
Expand Down
3 changes: 2 additions & 1 deletion nipype/interfaces/base/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import simplejson as json
from dateutil.parser import parse as parseutc
from future import standard_library
from traits.trait_errors import TraitError

from ... import config, logging, LooseVersion
from ...utils.provenance import write_provenance
Expand All @@ -37,7 +38,7 @@

from ...external.due import due

from .traits_extension import traits, isdefined, TraitError
from .traits_extension import traits, isdefined
from .specs import (BaseInterfaceInputSpec, CommandLineInputSpec,
StdOutCommandLineInputSpec, MpiCommandLineInputSpec,
get_filecopy_info)
Expand Down
68 changes: 41 additions & 27 deletions nipype/interfaces/base/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,20 @@
from builtins import str, bytes
from packaging.version import Version

from ...utils.filemanip import md5, hash_infile, hash_timestamp, to_str
from traits.trait_errors import TraitError
from traits.trait_handlers import TraitDictObject, TraitListObject
from ...utils.filemanip import (
md5, hash_infile, hash_timestamp, to_str, USING_PATHLIB2)
from .traits_extension import (
traits,
Undefined,
isdefined,
TraitError,
TraitDictObject,
TraitListObject,
has_metadata,
)

from ... import config, __version__


FLOAT_FORMAT = '{:.10f}'.format
nipype_version = Version(__version__)

Expand Down Expand Up @@ -314,6 +315,39 @@ def __all__(self):
return self.copyable_trait_names()


def _deepcopypatch(self, memo):
"""
Replace the ``__deepcopy__`` member with a traits-friendly implementation.

A bug in ``__deepcopy__`` for ``HasTraits`` results in weird cloning behaviors.
Occurs for all specs in Python<3 and only for DynamicTraitedSpec in Python>2.

"""
id_self = id(self)
if id_self in memo:
return memo[id_self]
dup_dict = deepcopy(self.trait_get(), memo)
# access all keys
for key in self.copyable_trait_names():
if key in self.__dict__.keys():
_ = getattr(self, key)
# clone once
dup = self.clone_traits(memo=memo)
for key in self.copyable_trait_names():
try:
_ = getattr(dup, key)
except:
pass
# clone twice
dup = self.clone_traits(memo=memo)
dup.trait_set(**dup_dict)
return dup


if USING_PATHLIB2:
BaseTraitedSpec.__deepcopy__ = _deepcopypatch


class TraitedSpec(BaseTraitedSpec):
""" Create a subclass with strict traits.

Expand All @@ -333,29 +367,9 @@ class DynamicTraitedSpec(BaseTraitedSpec):
functioning well together.
"""

def __deepcopy__(self, memo):
""" bug in deepcopy for HasTraits results in weird cloning behavior for
added traits
"""
id_self = id(self)
if id_self in memo:
return memo[id_self]
dup_dict = deepcopy(self.trait_get(), memo)
# access all keys
for key in self.copyable_trait_names():
if key in self.__dict__.keys():
_ = getattr(self, key)
# clone once
dup = self.clone_traits(memo=memo)
for key in self.copyable_trait_names():
try:
_ = getattr(dup, key)
except:
pass
# clone twice
dup = self.clone_traits(memo=memo)
dup.trait_set(**dup_dict)
return dup

if not USING_PATHLIB2:
DynamicTraitedSpec.__deepcopy__ = _deepcopypatch


class CommandLineInputSpec(BaseInterfaceInputSpec):
Expand Down
2 changes: 1 addition & 1 deletion nipype/interfaces/base/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def _inputs_help(cls):

>>> from nipype.interfaces.afni import GCOR
>>> _inputs_help(GCOR) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
['Inputs::', '', '\t[Mandatory]', '\tin_file: (an existing file name)', ...
['Inputs::', '', '\t[Mandatory]', '\tin_file: (a pathlike object or string...

"""
helpstr = ['Inputs::']
Expand Down
9 changes: 4 additions & 5 deletions nipype/interfaces/base/tests/test_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
from __future__ import print_function, unicode_literals
from future import standard_library
import os
import warnings
from future import standard_library

import pytest

Expand Down Expand Up @@ -420,18 +420,17 @@ def test_ImageFile():
# setup traits
x.add_trait('nifti', nib.ImageFile(types=['nifti1', 'dicom']))
x.add_trait('anytype', nib.ImageFile())
x.add_trait('newtype', nib.ImageFile(types=['nifti10']))
with pytest.raises(ValueError):
x.add_trait('newtype', nib.ImageFile(types=['nifti10']))
x.add_trait('nocompress',
nib.ImageFile(types=['mgh'], allow_compressed=False))

with pytest.raises(nib.TraitError):
x.nifti = 'test.mgz'
x.nifti = 'test.nii'
x.anytype = 'test.xml'
with pytest.raises(AttributeError):
x.newtype = 'test.nii'
with pytest.raises(nib.TraitError):
x.nocompress = 'test.nii.gz'
x.nocompress = 'test.mgz'
x.nocompress = 'test.mgh'


Expand Down
Loading