From 8292a7afcd44dd06b6da4fa80d04e8e596743644 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 18 Jul 2019 23:27:10 -0700 Subject: [PATCH 1/9] enh: add resolving to the results loader and rebasing to saver Fixes #2944. --- nipype/pipeline/engine/tests/test_utils.py | 59 +++++++++++++++++ nipype/pipeline/engine/utils.py | 73 +++++++++++++++------- 2 files changed, 111 insertions(+), 21 deletions(-) diff --git a/nipype/pipeline/engine/tests/test_utils.py b/nipype/pipeline/engine/tests/test_utils.py index 4f4383f169..c462ea1533 100644 --- a/nipype/pipeline/engine/tests/test_utils.py +++ b/nipype/pipeline/engine/tests/test_utils.py @@ -224,3 +224,62 @@ def test_mapnode_crash3(tmpdir): wf.config["execution"]["crashdump_dir"] = os.getcwd() with pytest.raises(RuntimeError): wf.run(plugin='Linear') + +class StrPathConfuserInputSpec(nib.TraitedSpec): + in_str = nib.traits.String() + + +class StrPathConfuserOutputSpec(nib.TraitedSpec): + out_tuple = nib.traits.Tuple(nib.File, nib.traits.String) + out_dict_path = nib.traits.Dict(nib.traits.String, nib.File(exists=True)) + out_dict_str = nib.traits.DictStrStr() + out_list = nib.traits.List(nib.traits.String) + out_str = nib.traits.String() + out_path = nib.File(exists=True) + + +class StrPathConfuser(nib.SimpleInterface): + input_spec = StrPathConfuserInputSpec + output_spec = StrPathConfuserOutputSpec + + def _run_interface(self, runtime): + out_path = os.path.abspath(os.path.basename(self.inputs.in_str) + '_path') + open(out_path, 'w').close() + self._results['out_str'] = self.inputs.in_str + self._results['out_path'] = out_path + self._results['out_tuple'] = (out_path, self.inputs.in_str) + self._results['out_dict_path'] = {self.inputs.in_str: out_path} + self._results['out_dict_str'] = {self.inputs.in_str: self.inputs.in_str} + self._results['out_list'] = [self.inputs.in_str] * 2 + return runtime + + +def test_modify_paths_bug(tmpdir): + """ + There was a bug in which, if the current working directory contained a file with the name + of an output String, the string would get transformed into a path, and generally wreak havoc. + This attempts to replicate that condition, using an object with strings and paths in various + trait configurations, to ensure that the guards added resolve the issue. + Please see https://github.com/nipy/nipype/issues/2944 for more details. + """ + tmpdir.chdir() + + spc = pe.Node(StrPathConfuser(in_str='2'), name='spc') + + open('2', 'w').close() + + outputs = spc.run().outputs + + # Basic check that string was not manipulated + out_str = outputs.out_str + assert out_str == '2' + + # Check path exists and is absolute + out_path = outputs.out_path + assert os.path.isabs(out_path) + + # Assert data structures pass through correctly + assert outputs.out_tuple == (out_path, out_str) + assert outputs.out_dict_path == {out_str: out_path} + assert outputs.out_dict_str == {out_str: out_str} + assert outputs.out_list == [out_str] * 2 diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index 638c91ed97..afd450ef68 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -25,13 +25,13 @@ from ... import logging, config, LooseVersion from ...utils.filemanip import ( Path, + indirectory, relpath, makedirs, fname_presuffix, to_str, ensure_list, get_related_files, - FileNotFoundError, save_json, savepkl, loadpkl, @@ -41,6 +41,7 @@ ) from ...utils.misc import str2bool from ...utils.functions import create_function_from_source +from ...interfaces.base.traits_extension import rebase_path_traits, resolve_path_traits from ...interfaces.base import (Bunch, CommandLine, isdefined, Undefined, InterfaceResult, traits) from ...interfaces.utility import IdentityInterface @@ -227,52 +228,82 @@ def write_report(node, report_type=None, is_mapnode=False): return -def save_resultfile(result, cwd, name): - """Save a result pklz file to ``cwd``""" +def save_resultfile(result, cwd, name, rebase=True): + """Save a result pklz file to ``cwd``.""" + cwd = os.path.abspath(cwd) resultsfile = os.path.join(cwd, 'result_%s.pklz' % name) - savepkl(resultsfile, result) - logger.debug('saved results in %s', resultsfile) + logger.debug("Saving results file: '%s'", resultsfile) + if result.outputs is None: + logger.warn('Storing result file without outputs') + savepkl(resultsfile, result) + return + try: + outputs = result.outputs.trait_get() + except AttributeError: + logger.debug('Storing non-traited results, skipping rebase of paths') + savepkl(resultsfile, result) + return -def load_resultfile(results_file): + try: + with indirectory(cwd): + # All the magic to fix #2944 resides here: + for key, val in list(outputs.items()): + val = rebase_path_traits(result.outputs.trait(key), val, cwd) + setattr(result.outputs, key, val) + savepkl(resultsfile, result) + finally: + # Reset resolved paths from the outputs dict no matter what + for key, val in list(outputs.items()): + setattr(result.outputs, key, val) + + +def load_resultfile(results_file, resolve=True): """ - Load InterfaceResult file from path + Load InterfaceResult file from path. Parameter --------- - path : base_dir of node name : name of node Returns ------- - result : InterfaceResult structure aggregate : boolean indicating whether node should aggregate_outputs attribute error : boolean indicating whether there was some mismatch in versions of traits used to store result and hence node needs to rerun + """ - aggregate = True results_file = Path(results_file) + aggregate = True result = None attribute_error = False - if results_file.exists(): + + if not results_file.exists(): + return result, aggregate, attribute_error + + with indirectory(str(results_file.parent)): try: result = loadpkl(results_file) - except (traits.TraitError, AttributeError, ImportError, - EOFError) as err: - if isinstance(err, (AttributeError, ImportError)): - attribute_error = True - logger.debug('attribute error: %s probably using ' - 'different trait pickled file', str(err)) - else: - logger.debug( - 'some file does not exist. hence trait cannot be set') + except (traits.TraitError, EOFError): + logger.debug( + 'some file does not exist. hence trait cannot be set') + except (AttributeError, ImportError) as err: + attribute_error = True + logger.debug('attribute error: %s probably using ' + 'different trait pickled file', str(err)) else: aggregate = False - logger.debug('Aggregate: %s', aggregate) + if resolve and not aggregate: + logger.debug('Resolving paths in outputs loaded from results file.') + for trait_name, old_value in list(result.outputs.get().items()): + value = resolve_path_traits(result.outputs.trait(trait_name), old_value, + results_file.parent) + setattr(result.outputs, trait_name, value) + return result, aggregate, attribute_error From c87f9525adb69f149c1cacf738f2fbb0d4e8c953 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 18 Jul 2019 23:58:36 -0700 Subject: [PATCH 2/9] fix: final fixups for tests to pass Modified ``test_outputmultipath_collapse`` due to a derivation of #2968. --- nipype/pipeline/engine/nodes.py | 2 +- nipype/pipeline/engine/tests/test_nodes.py | 10 +++++----- nipype/pipeline/engine/utils.py | 9 +++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 6314411a07..e90f1dbb51 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -1260,7 +1260,7 @@ def _run_interface(self, execute=True, updatehash=False): stop_first=str2bool( self.config['execution']['stop_on_first_crash']))) # And store results - _save_resultfile(result, cwd, self.name) + _save_resultfile(result, cwd, self.name, rebase=False) # remove any node directories no longer required dirs2remove = [] for path in glob(op.join(cwd, 'mapflow', '*')): diff --git a/nipype/pipeline/engine/tests/test_nodes.py b/nipype/pipeline/engine/tests/test_nodes.py index ea03fe69ae..53531eb5c4 100644 --- a/nipype/pipeline/engine/tests/test_nodes.py +++ b/nipype/pipeline/engine/tests/test_nodes.py @@ -295,13 +295,13 @@ def test_inputs_removal(tmpdir): def test_outputmultipath_collapse(tmpdir): """Test an OutputMultiPath whose initial value is ``[[x]]`` to ensure that it is returned as ``[x]``, regardless of how accessed.""" - select_if = niu.Select(inlist=[[1, 2, 3], [4]], index=1) - select_nd = pe.Node(niu.Select(inlist=[[1, 2, 3], [4]], index=1), + select_if = niu.Select(inlist=[[1, 2, 3], [4, 5]], index=1) + select_nd = pe.Node(niu.Select(inlist=[[1, 2, 3], [4, 5]], index=1), name='select_nd') ifres = select_if.run() ndres = select_nd.run() - assert ifres.outputs.out == [4] - assert ndres.outputs.out == [4] - assert select_nd.result.outputs.out == [4] + assert ifres.outputs.out == [4, 5] + assert ndres.outputs.out == [4, 5] + assert select_nd.result.outputs.out == [4, 5] diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index afd450ef68..0fb1d0c85d 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -297,9 +297,14 @@ def load_resultfile(results_file, resolve=True): else: aggregate = False - if resolve and not aggregate: + if resolve and result.outputs: + try: + outputs = result.outputs.get() + except TypeError: # This is a Bunch + return result, aggregate, attribute_error + logger.debug('Resolving paths in outputs loaded from results file.') - for trait_name, old_value in list(result.outputs.get().items()): + for trait_name, old_value in list(outputs.items()): value = resolve_path_traits(result.outputs.trait(trait_name), old_value, results_file.parent) setattr(result.outputs, trait_name, value) From ece8f08cf0d6e635fc2c93c7057f7092dc255deb Mon Sep 17 00:00:00 2001 From: oesteban Date: Fri, 19 Jul 2019 09:06:38 -0700 Subject: [PATCH 3/9] fix: reset only changed paths (workaround to preempt #2968) --- nipype/pipeline/engine/utils.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index 0fb1d0c85d..7879428816 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -248,12 +248,13 @@ def save_resultfile(result, cwd, name, rebase=True): try: with indirectory(cwd): # All the magic to fix #2944 resides here: - for key, val in list(outputs.items()): - val = rebase_path_traits(result.outputs.trait(key), val, cwd) - setattr(result.outputs, key, val) + for key, old in list(outputs.items()): + val = rebase_path_traits(result.outputs.trait(key), old, cwd) + if old != val: # Workaround #2968: Reset only changed values + setattr(result.outputs, key, val) savepkl(resultsfile, result) finally: - # Reset resolved paths from the outputs dict no matter what + # Restore resolved paths from the outputs dict no matter what for key, val in list(outputs.items()): setattr(result.outputs, key, val) @@ -297,17 +298,18 @@ def load_resultfile(results_file, resolve=True): else: aggregate = False - if resolve and result.outputs: - try: - outputs = result.outputs.get() - except TypeError: # This is a Bunch - return result, aggregate, attribute_error - - logger.debug('Resolving paths in outputs loaded from results file.') - for trait_name, old_value in list(outputs.items()): - value = resolve_path_traits(result.outputs.trait(trait_name), old_value, - results_file.parent) - setattr(result.outputs, trait_name, value) + if resolve and result.outputs: + try: + outputs = result.outputs.get() + except TypeError: # This is a Bunch + return result, aggregate, attribute_error + + logger.debug('Resolving paths in outputs loaded from results file.') + for trait_name, old in list(outputs.items()): + value = resolve_path_traits(result.outputs.trait(trait_name), old, + results_file.parent) + if value != old: # Workaround #2968: Reset only changed values + setattr(result.outputs, trait_name, value) return result, aggregate, attribute_error From 4c813049e990ef7962f0bb1daf004885e9eb88d2 Mon Sep 17 00:00:00 2001 From: oesteban Date: Wed, 31 Jul 2019 18:33:00 -0700 Subject: [PATCH 4/9] fix: do not set ``Undefined`` values - fixes join nodes with remove_needed_outputs=true --- nipype/pipeline/engine/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index 7879428816..56beb70c73 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -249,14 +249,15 @@ def save_resultfile(result, cwd, name, rebase=True): with indirectory(cwd): # All the magic to fix #2944 resides here: for key, old in list(outputs.items()): - val = rebase_path_traits(result.outputs.trait(key), old, cwd) - if old != val: # Workaround #2968: Reset only changed values + if isdefined(old): + val = rebase_path_traits(result.outputs.trait(key), old, cwd) setattr(result.outputs, key, val) savepkl(resultsfile, result) finally: # Restore resolved paths from the outputs dict no matter what for key, val in list(outputs.items()): - setattr(result.outputs, key, val) + if isdefined(val): + setattr(result.outputs, key, val) def load_resultfile(results_file, resolve=True): @@ -306,9 +307,9 @@ def load_resultfile(results_file, resolve=True): logger.debug('Resolving paths in outputs loaded from results file.') for trait_name, old in list(outputs.items()): - value = resolve_path_traits(result.outputs.trait(trait_name), old, - results_file.parent) - if value != old: # Workaround #2968: Reset only changed values + if isdefined(old): + value = resolve_path_traits(result.outputs.trait(trait_name), old, + results_file.parent) setattr(result.outputs, trait_name, value) return result, aggregate, attribute_error From 3ad68d7ef02e2d6b337a5e83fd5e7f401c76b3d5 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 1 Aug 2019 09:27:04 -0700 Subject: [PATCH 5/9] fix: reset test_nodes so that the OutputMultiObject issue surfaces Once we figure out the problem of ``OutputMultiObject``, we could go ahead and set fix #2944, fix poldracklab/fmriprep#1674, close #2945. --- nipype/pipeline/engine/tests/test_nodes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nipype/pipeline/engine/tests/test_nodes.py b/nipype/pipeline/engine/tests/test_nodes.py index 53531eb5c4..ea03fe69ae 100644 --- a/nipype/pipeline/engine/tests/test_nodes.py +++ b/nipype/pipeline/engine/tests/test_nodes.py @@ -295,13 +295,13 @@ def test_inputs_removal(tmpdir): def test_outputmultipath_collapse(tmpdir): """Test an OutputMultiPath whose initial value is ``[[x]]`` to ensure that it is returned as ``[x]``, regardless of how accessed.""" - select_if = niu.Select(inlist=[[1, 2, 3], [4, 5]], index=1) - select_nd = pe.Node(niu.Select(inlist=[[1, 2, 3], [4, 5]], index=1), + select_if = niu.Select(inlist=[[1, 2, 3], [4]], index=1) + select_nd = pe.Node(niu.Select(inlist=[[1, 2, 3], [4]], index=1), name='select_nd') ifres = select_if.run() ndres = select_nd.run() - assert ifres.outputs.out == [4, 5] - assert ndres.outputs.out == [4, 5] - assert select_nd.result.outputs.out == [4, 5] + assert ifres.outputs.out == [4] + assert ndres.outputs.out == [4] + assert select_nd.result.outputs.out == [4] From 829957c0874803c521c8e75ea23f4e4909ef5ce9 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 1 Aug 2019 11:40:29 -0700 Subject: [PATCH 6/9] enh: read true value from traits --- nipype/pipeline/engine/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index 56beb70c73..de15ee6dcb 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -245,19 +245,21 @@ def save_resultfile(result, cwd, name, rebase=True): savepkl(resultsfile, result) return + backup_traits = {} try: with indirectory(cwd): # All the magic to fix #2944 resides here: for key, old in list(outputs.items()): if isdefined(old): + old = result.outputs.trait(key).handler.get_value(result.outputs, key) + backup_traits[key] = old val = rebase_path_traits(result.outputs.trait(key), old, cwd) setattr(result.outputs, key, val) savepkl(resultsfile, result) finally: # Restore resolved paths from the outputs dict no matter what - for key, val in list(outputs.items()): - if isdefined(val): - setattr(result.outputs, key, val) + for key, val in list(backup_traits.items()): + setattr(result.outputs, key, val) def load_resultfile(results_file, resolve=True): @@ -308,6 +310,8 @@ def load_resultfile(results_file, resolve=True): logger.debug('Resolving paths in outputs loaded from results file.') for trait_name, old in list(outputs.items()): if isdefined(old): + old = result.outputs.trait(trait_name).handler.get_value( + result.outputs, trait_name) value = resolve_path_traits(result.outputs.trait(trait_name), old, results_file.parent) setattr(result.outputs, trait_name, value) From 5f917f22e855dcbba79883500d0fe22de2cfecf7 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 1 Aug 2019 16:47:51 -0700 Subject: [PATCH 7/9] fix: final touches to the PR Close #2944. Close #2949. --- nipype/caching/tests/test_memory.py | 3 +-- nipype/pipeline/engine/tests/test_base.py | 8 ++------ nipype/pipeline/engine/utils.py | 21 +++++++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nipype/caching/tests/test_memory.py b/nipype/caching/tests/test_memory.py index 3ea594f22a..642fee363d 100644 --- a/nipype/caching/tests/test_memory.py +++ b/nipype/caching/tests/test_memory.py @@ -15,8 +15,7 @@ class SideEffectInterface(EngineTestInterface): def _run_interface(self, runtime): global nb_runs nb_runs += 1 - runtime.returncode = 0 - return runtime + return super(SideEffectInterface, self)._run_interface(runtime) def test_caching(tmpdir): diff --git a/nipype/pipeline/engine/tests/test_base.py b/nipype/pipeline/engine/tests/test_base.py index 3513152c06..5b072e9ee6 100644 --- a/nipype/pipeline/engine/tests/test_base.py +++ b/nipype/pipeline/engine/tests/test_base.py @@ -20,19 +20,15 @@ class OutputSpec(nib.TraitedSpec): output1 = nib.traits.List(nib.traits.Int, desc='outputs') -class EngineTestInterface(nib.BaseInterface): +class EngineTestInterface(nib.SimpleInterface): input_spec = InputSpec output_spec = OutputSpec def _run_interface(self, runtime): runtime.returncode = 0 + self._results['output1'] = [1, self.inputs.input1] return runtime - def _list_outputs(self): - outputs = self._outputs().get() - outputs['output1'] = [1, self.inputs.input1] - return outputs - @pytest.mark.parametrize( 'name', ['valid1', 'valid_node', 'valid-node', 'ValidNode0']) diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index de15ee6dcb..f7b2772f50 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -41,9 +41,10 @@ ) from ...utils.misc import str2bool from ...utils.functions import create_function_from_source -from ...interfaces.base.traits_extension import rebase_path_traits, resolve_path_traits -from ...interfaces.base import (Bunch, CommandLine, isdefined, Undefined, - InterfaceResult, traits) +from ...interfaces.base.traits_extension import ( + rebase_path_traits, resolve_path_traits, OutputMultiPath, isdefined, Undefined, traits) +from ...interfaces.base.support import Bunch, InterfaceResult +from ...interfaces.base import CommandLine from ...interfaces.utility import IdentityInterface from ...utils.provenance import ProvStore, pm, nipype_ns, get_id @@ -239,7 +240,7 @@ def save_resultfile(result, cwd, name, rebase=True): savepkl(resultsfile, result) return try: - outputs = result.outputs.trait_get() + output_names = result.outputs.copyable_trait_names() except AttributeError: logger.debug('Storing non-traited results, skipping rebase of paths') savepkl(resultsfile, result) @@ -249,9 +250,12 @@ def save_resultfile(result, cwd, name, rebase=True): try: with indirectory(cwd): # All the magic to fix #2944 resides here: - for key, old in list(outputs.items()): + for key in output_names: + old = getattr(result.outputs, key) if isdefined(old): - old = result.outputs.trait(key).handler.get_value(result.outputs, key) + if result.outputs.trait(key).is_trait_type(OutputMultiPath): + old = result.outputs.trait(key).handler.get_value( + result.outputs, key) backup_traits[key] = old val = rebase_path_traits(result.outputs.trait(key), old, cwd) setattr(result.outputs, key, val) @@ -310,8 +314,9 @@ def load_resultfile(results_file, resolve=True): logger.debug('Resolving paths in outputs loaded from results file.') for trait_name, old in list(outputs.items()): if isdefined(old): - old = result.outputs.trait(trait_name).handler.get_value( - result.outputs, trait_name) + if result.outputs.trait(trait_name).is_trait_type(OutputMultiPath): + old = result.outputs.trait(trait_name).handler.get_value( + result.outputs, trait_name) value = resolve_path_traits(result.outputs.trait(trait_name), old, results_file.parent) setattr(result.outputs, trait_name, value) From a06166d9800ec7ff07492a6cf36f276d0e53d11c Mon Sep 17 00:00:00 2001 From: oesteban Date: Fri, 2 Aug 2019 15:07:44 -0700 Subject: [PATCH 8/9] fix: return same type of value for traits containing lists --- nipype/interfaces/base/traits_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/base/traits_extension.py b/nipype/interfaces/base/traits_extension.py index 395ae0732e..27c4103a74 100644 --- a/nipype/interfaces/base/traits_extension.py +++ b/nipype/interfaces/base/traits_extension.py @@ -526,7 +526,7 @@ def _recurse_on_path_traits(func, thistrait, value, cwd): elif thistrait.is_trait_type(traits.List): innertrait, = thistrait.inner_traits if not isinstance(value, (list, tuple)): - value = [value] + return _recurse_on_path_traits(func, innertrait, value, cwd) value = [_recurse_on_path_traits(func, innertrait, v, cwd) for v in value] From 6dee6074ba49b7593a9ad9ab72c978be507dfc06 Mon Sep 17 00:00:00 2001 From: oesteban Date: Wed, 7 Aug 2019 12:27:26 -0700 Subject: [PATCH 9/9] fix: honor ``use_relative_paths`` option --- nipype/pipeline/engine/nodes.py | 16 ++++++++++++---- nipype/pipeline/engine/utils.py | 5 ++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index e90f1dbb51..2c441a5c57 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -587,7 +587,9 @@ def _load_results(self): runtime=runtime, inputs=self._interface.inputs.get_traitsfree(), outputs=aggouts) - _save_resultfile(result, cwd, self.name) + _save_resultfile( + result, cwd, self.name, + rebase=str2bool(self.config['execution']['use_relative_paths'])) else: logger.debug('aggregating mapnode results') result = self._run_interface() @@ -634,7 +636,9 @@ def _run_command(self, execute, copyfiles=True): except Exception as msg: result.runtime.stderr = '{}\n\n{}'.format( getattr(result.runtime, 'stderr', ''), msg) - _save_resultfile(result, outdir, self.name) + _save_resultfile( + result, outdir, self.name, + rebase=str2bool(self.config['execution']['use_relative_paths'])) raise cmdfile = op.join(outdir, 'command.txt') with open(cmdfile, 'wt') as fd: @@ -646,7 +650,9 @@ def _run_command(self, execute, copyfiles=True): except Exception as msg: result.runtime.stderr = '%s\n\n%s'.format( getattr(result.runtime, 'stderr', ''), msg) - _save_resultfile(result, outdir, self.name) + _save_resultfile( + result, outdir, self.name, + rebase=str2bool(self.config['execution']['use_relative_paths'])) raise dirs2keep = None @@ -660,7 +666,9 @@ def _run_command(self, execute, copyfiles=True): self.needed_outputs, self.config, dirs2keep=dirs2keep) - _save_resultfile(result, outdir, self.name) + _save_resultfile( + result, outdir, self.name, + rebase=str2bool(self.config['execution']['use_relative_paths'])) return result diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py index f7b2772f50..65170f14c9 100644 --- a/nipype/pipeline/engine/utils.py +++ b/nipype/pipeline/engine/utils.py @@ -229,8 +229,11 @@ def write_report(node, report_type=None, is_mapnode=False): return -def save_resultfile(result, cwd, name, rebase=True): +def save_resultfile(result, cwd, name, rebase=None): """Save a result pklz file to ``cwd``.""" + if rebase is None: + rebase = config.getboolean('execution', 'use_relative_paths') + cwd = os.path.abspath(cwd) resultsfile = os.path.join(cwd, 'result_%s.pklz' % name) logger.debug("Saving results file: '%s'", resultsfile)