Skip to content

Commit 11f8eb2

Browse files
authored
Merge pull request #2798 from oesteban/maint/strip-get-filecopy-info
[MAINT] Outsource ``get_filecopy_info()`` from interfaces
2 parents fa94344 + d9d55b7 commit 11f8eb2

File tree

7 files changed

+88
-44
lines changed

7 files changed

+88
-44
lines changed

nipype/interfaces/base/core.py

+9-17
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from .specs import (BaseInterfaceInputSpec, CommandLineInputSpec,
4343
StdOutCommandLineInputSpec, MpiCommandLineInputSpec)
4444
from .support import (Bunch, InterfaceResult, NipypeInterfaceError)
45+
from .specs import get_filecopy_info
4546

4647
from future import standard_library
4748
standard_library.install_aliases()
@@ -122,11 +123,15 @@ def _list_outputs(self):
122123
""" List expected outputs"""
123124
raise NotImplementedError
124125

125-
def _get_filecopy_info(self):
126-
""" Provides information about file inputs to copy or link to cwd.
127-
Necessary for pipeline operation
126+
@classmethod
127+
def _get_filecopy_info(cls):
128+
"""Provides information about file inputs to copy or link to cwd.
129+
Necessary for pipeline operation
128130
"""
129-
raise NotImplementedError
131+
iflogger.warning(
132+
'_get_filecopy_info member of Interface was deprecated '
133+
'in nipype-1.1.6 and will be removed in 1.2.0')
134+
return get_filecopy_info(cls)
130135

131136

132137
class BaseInterface(Interface):
@@ -330,19 +335,6 @@ def _outputs(self):
330335

331336
return outputs
332337

333-
@classmethod
334-
def _get_filecopy_info(cls):
335-
""" Provides information about file inputs to copy or link to cwd.
336-
Necessary for pipeline operation
337-
"""
338-
info = []
339-
if cls.input_spec is None:
340-
return info
341-
metadata = dict(copyfile=lambda t: t is not None)
342-
for name, spec in sorted(cls.input_spec().traits(**metadata).items()):
343-
info.append(dict(key=name, copy=spec.copyfile))
344-
return info
345-
346338
def _check_requires(self, spec, name, value):
347339
""" check if required inputs are satisfied
348340
"""

nipype/interfaces/base/specs.py

+19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
absolute_import)
1414

1515
import os
16+
from inspect import isclass
1617
from copy import deepcopy
1718
from warnings import warn
1819
from builtins import str, bytes
@@ -375,3 +376,21 @@ class MpiCommandLineInputSpec(CommandLineInputSpec):
375376
n_procs = traits.Int(desc="Num processors to specify to mpiexec. Do not "
376377
"specify if this is managed externally (e.g. through "
377378
"SGE)")
379+
380+
381+
def get_filecopy_info(cls):
382+
"""Provides information about file inputs to copy or link to cwd.
383+
Necessary for pipeline operation
384+
"""
385+
if cls.input_spec is None:
386+
return None
387+
388+
# normalize_filenames is not a classmethod, hence check first
389+
if not isclass(cls) and hasattr(cls, 'normalize_filenames'):
390+
cls.normalize_filenames()
391+
info = []
392+
inputs = cls.input_spec() if isclass(cls) else cls.inputs
393+
metadata = dict(copyfile=lambda t: t is not None)
394+
for name, spec in sorted(inputs.traits(**metadata).items()):
395+
info.append(dict(key=name, copy=spec.copyfile))
396+
return info

nipype/interfaces/base/tests/test_core.py

-7
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,12 @@ def __init__(self):
6262
nif.aggregate_outputs()
6363
with pytest.raises(NotImplementedError):
6464
nif._list_outputs()
65-
with pytest.raises(NotImplementedError):
66-
nif._get_filecopy_info()
6765

6866

6967
def test_BaseInterface():
7068
config.set('monitoring', 'enable', '0')
7169

7270
assert nib.BaseInterface.help() is None
73-
assert nib.BaseInterface._get_filecopy_info() == []
7471

7572
class InputSpec(nib.TraitedSpec):
7673
foo = nib.traits.Int(desc='a random int')
@@ -90,10 +87,6 @@ class DerivedInterface(nib.BaseInterface):
9087
assert DerivedInterface.help() is None
9188
assert 'moo' in ''.join(DerivedInterface._inputs_help())
9289
assert DerivedInterface()._outputs() is None
93-
assert DerivedInterface._get_filecopy_info()[0]['key'] == 'woo'
94-
assert DerivedInterface._get_filecopy_info()[0]['copy']
95-
assert DerivedInterface._get_filecopy_info()[1]['key'] == 'zoo'
96-
assert not DerivedInterface._get_filecopy_info()[1]['copy']
9790
assert DerivedInterface().inputs.foo == nib.Undefined
9891
with pytest.raises(ValueError):
9992
DerivedInterface()._check_mandatory_inputs()

nipype/interfaces/base/tests/test_specs.py

+45-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ....interfaces import fsl
1515
from ...utility.wrappers import Function
1616
from ....pipeline import Node
17-
17+
from ..specs import get_filecopy_info
1818

1919
standard_library.install_aliases()
2020

@@ -55,8 +55,8 @@ def test_TraitedSpec_tab_completion():
5555
bet_nd = Node(fsl.BET(), name='bet')
5656
bet_interface = fsl.BET()
5757
bet_inputs = bet_nd.inputs.class_editable_traits()
58-
bet_outputs = bet_nd.outputs.class_editable_traits()
59-
58+
bet_outputs = bet_nd.outputs.class_editable_traits()
59+
6060
# Check __all__ for bet node and interface inputs
6161
assert set(bet_nd.inputs.__all__) == set(bet_inputs)
6262
assert set(bet_interface.inputs.__all__) == set(bet_inputs)
@@ -433,3 +433,45 @@ def test_ImageFile():
433433
with pytest.raises(nib.TraitError):
434434
x.nocompress = 'test.nii.gz'
435435
x.nocompress = 'test.mgh'
436+
437+
438+
def test_filecopy_info():
439+
class InputSpec(nib.TraitedSpec):
440+
foo = nib.traits.Int(desc='a random int')
441+
goo = nib.traits.Int(desc='a random int', mandatory=True)
442+
moo = nib.traits.Int(desc='a random int', mandatory=False)
443+
hoo = nib.traits.Int(desc='a random int', usedefault=True)
444+
zoo = nib.File(desc='a file', copyfile=False)
445+
woo = nib.File(desc='a file', copyfile=True)
446+
447+
class DerivedInterface(nib.BaseInterface):
448+
input_spec = InputSpec
449+
resource_monitor = False
450+
451+
def normalize_filenames(self):
452+
"""A mock normalize_filenames for freesurfer interfaces that have one"""
453+
self.inputs.zoo = 'normalized_filename.ext'
454+
455+
assert get_filecopy_info(nib.BaseInterface) == []
456+
457+
# Test on interface class, not instantiated
458+
info = get_filecopy_info(DerivedInterface)
459+
assert info[0]['key'] == 'woo'
460+
assert info[0]['copy']
461+
assert info[1]['key'] == 'zoo'
462+
assert not info[1]['copy']
463+
info = None
464+
465+
# Test with instantiated interface
466+
derived = DerivedInterface()
467+
# First check that zoo is not defined
468+
assert derived.inputs.zoo == Undefined
469+
# After the first call to get_filecopy_info zoo is defined
470+
info = get_filecopy_info(derived)
471+
# Ensure that normalize_filenames was called
472+
assert derived.inputs.zoo == 'normalized_filename.ext'
473+
# Check the results are consistent
474+
assert info[0]['key'] == 'woo'
475+
assert info[0]['copy']
476+
assert info[1]['key'] == 'zoo'
477+
assert not info[1]['copy']

nipype/interfaces/freesurfer/base.py

-10
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,6 @@ class FSSurfaceCommand(FSCommand):
189189
including the full path in the filename, we can also avoid this behavior.
190190
"""
191191

192-
def _get_filecopy_info(self):
193-
self._normalize_filenames()
194-
return super(FSSurfaceCommand, self)._get_filecopy_info()
195-
196-
def _normalize_filenames(self):
197-
"""Filename normalization routine to perform only when run in Node
198-
context
199-
"""
200-
pass
201-
202192
@staticmethod
203193
def _associated_file(in_file, out_name):
204194
"""Based on MRIsBuildFileName in freesurfer/utils/mrisurf.c

nipype/interfaces/freesurfer/utils.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -1289,9 +1289,11 @@ def _list_outputs(self):
12891289

12901290
return outputs
12911291

1292-
def _normalize_filenames(self):
1293-
""" In a Node context, interpret out_file as a literal path to
1294-
reduce surprise.
1292+
def normalize_filenames(self):
1293+
"""
1294+
Filename normalization routine to perform only when run in Node
1295+
context.
1296+
Interpret out_file as a literal path to reduce surprise.
12951297
"""
12961298
if isdefined(self.inputs.out_file):
12971299
self.inputs.out_file = os.path.abspath(self.inputs.out_file)
@@ -3837,8 +3839,11 @@ def _list_outputs(self):
38373839
self.inputs.out_name)
38383840
return outputs
38393841

3840-
def _normalize_filenames(self):
3841-
""" Find full paths for pial, thickness and sphere files for copying
3842+
def normalize_filenames(self):
3843+
"""
3844+
Filename normalization routine to perform only when run in Node
3845+
context.
3846+
Find full paths for pial, thickness and sphere files for copying.
38423847
"""
38433848
in_file = self.inputs.in_file
38443849

nipype/pipeline/engine/nodes.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from ...interfaces.base import (traits, InputMultiPath, CommandLine, Undefined,
3333
DynamicTraitedSpec, Bunch, InterfaceResult,
3434
Interface, isdefined)
35+
from ...interfaces.base.specs import get_filecopy_info
36+
3537
from .utils import (
3638
_parameterization_dir, save_hashfile as _save_hashfile, load_resultfile as
3739
_load_resultfile, save_resultfile as _save_resultfile, nodelist_runner as
@@ -656,7 +658,8 @@ def _run_command(self, execute, copyfiles=True):
656658

657659
def _copyfiles_to_wd(self, execute=True, linksonly=False):
658660
"""copy files over and change the inputs"""
659-
if not hasattr(self._interface, '_get_filecopy_info'):
661+
filecopy_info = get_filecopy_info(self.interface)
662+
if not filecopy_info:
660663
# Nothing to be done
661664
return
662665

@@ -669,7 +672,7 @@ def _copyfiles_to_wd(self, execute=True, linksonly=False):
669672
outdir = op.join(outdir, '_tempinput')
670673
makedirs(outdir, exist_ok=True)
671674

672-
for info in self._interface._get_filecopy_info():
675+
for info in filecopy_info:
673676
files = self.inputs.trait_get().get(info['key'])
674677
if not isdefined(files) or not files:
675678
continue

0 commit comments

Comments
 (0)