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 1 commit
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
Next Next commit
ENH: Modify Directory and File traits to get along with pathlib
Closes #2959
  • Loading branch information
oesteban committed Jul 16, 2019
commit 052461d2a9ae2e04e23750fdcd38848e675e6e53
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
72 changes: 46 additions & 26 deletions nipype/interfaces/base/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@
from builtins import str, bytes
from packaging.version import Version

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

from ... import config, __version__


USING_PATHLIB2 = False
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path # noqa
USING_PATHLIB2 = True

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

Expand Down Expand Up @@ -314,6 +321,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 +373,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