Skip to content

Commit 3c9c64f

Browse files
authored
Merge pull request #3395 from oesteban/fix/commandline-nodes-issue
FIX: Improve error handling of ``CommandLine`` interfaces
2 parents e497b0f + 5d9adbb commit 3c9c64f

File tree

3 files changed

+44
-9
lines changed

3 files changed

+44
-9
lines changed

nipype/interfaces/base/core.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ class must be instantiated with a command argument
607607
_cmd = None
608608
_version = None
609609
_terminal_output = "stream"
610+
_write_cmdline = False
610611

611612
@classmethod
612613
def set_default_terminal_output(cls, output_type):
@@ -623,7 +624,9 @@ def set_default_terminal_output(cls, output_type):
623624
else:
624625
raise AttributeError("Invalid terminal output_type: %s" % output_type)
625626

626-
def __init__(self, command=None, terminal_output=None, **inputs):
627+
def __init__(
628+
self, command=None, terminal_output=None, write_cmdline=False, **inputs
629+
):
627630
super(CommandLine, self).__init__(**inputs)
628631
self._environ = None
629632
# Set command. Input argument takes precedence
@@ -638,6 +641,8 @@ def __init__(self, command=None, terminal_output=None, **inputs):
638641
if terminal_output is not None:
639642
self.terminal_output = terminal_output
640643

644+
self._write_cmdline = write_cmdline
645+
641646
@property
642647
def cmd(self):
643648
"""sets base command, immutable"""
@@ -669,6 +674,14 @@ def terminal_output(self, value):
669674
)
670675
self._terminal_output = value
671676

677+
@property
678+
def write_cmdline(self):
679+
return self._write_cmdline
680+
681+
@write_cmdline.setter
682+
def write_cmdline(self, value):
683+
self._write_cmdline = value is True
684+
672685
def raise_exception(self, runtime):
673686
raise RuntimeError(
674687
(
@@ -716,9 +729,16 @@ def _run_interface(self, runtime, correct_return_codes=(0,)):
716729
adds stdout, stderr, merged, cmdline, dependencies, command_path
717730
718731
"""
719-
720732
out_environ = self._get_environ()
721733
# Initialize runtime Bunch
734+
735+
try:
736+
runtime.cmdline = self.cmdline
737+
except Exception as exc:
738+
raise RuntimeError(
739+
"Error raised when interpolating the command line"
740+
) from exc
741+
722742
runtime.stdout = None
723743
runtime.stderr = None
724744
runtime.cmdline = self.cmdline
@@ -742,7 +762,11 @@ def _run_interface(self, runtime, correct_return_codes=(0,)):
742762
if self._ldd
743763
else "<skipped>"
744764
)
745-
runtime = run_command(runtime, output=self.terminal_output)
765+
runtime = run_command(
766+
runtime,
767+
output=self.terminal_output,
768+
write_cmdline=self.write_cmdline,
769+
)
746770
return runtime
747771

748772
def _format_arg(self, name, trait_spec, value):
@@ -907,7 +931,14 @@ def _parse_inputs(self, skip=None):
907931

908932
if not isdefined(value):
909933
continue
910-
arg = self._format_arg(name, spec, value)
934+
935+
try:
936+
arg = self._format_arg(name, spec, value)
937+
except Exception as exc:
938+
raise ValueError(
939+
f"Error formatting command line argument '{name}' with value '{value}'"
940+
) from exc
941+
911942
if arg is None:
912943
continue
913944
pos = spec.position

nipype/pipeline/engine/nodes.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ def __init__(
209209
self.needed_outputs = needed_outputs
210210
self.config = None
211211

212+
if hasattr(self._interface, "write_cmdline"):
213+
self._interface.write_cmdline = True
214+
212215
@property
213216
def interface(self):
214217
"""Return the underlying interface object"""
@@ -711,16 +714,13 @@ def _run_command(self, execute, copyfiles=True):
711714
f'[Node] Executing "{self.name}" <{self._interface.__module__}'
712715
f".{self._interface.__class__.__name__}>"
713716
)
717+
714718
# Invoke core run method of the interface ignoring exceptions
715719
result = self._interface.run(cwd=outdir, ignore_exception=True)
716720
logger.info(
717721
f'[Node] Finished "{self.name}", elapsed time {result.runtime.duration}s.'
718722
)
719723

720-
if issubclass(self._interface.__class__, CommandLine):
721-
# Write out command line as it happened
722-
Path.write_text(outdir / "command.txt", f"{result.runtime.cmdline}\n")
723-
724724
exc_tb = getattr(result.runtime, "traceback", None)
725725

726726
if not exc_tb:

nipype/utils/subprocess.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import select
1111
import locale
1212
import datetime
13+
from pathlib import Path
1314
from subprocess import Popen, STDOUT, PIPE
1415
from .filemanip import canonicalize_env, read_stream
1516

@@ -69,7 +70,7 @@ def _read(self, drain):
6970
self._lastidx = len(self._rows)
7071

7172

72-
def run_command(runtime, output=None, timeout=0.01):
73+
def run_command(runtime, output=None, timeout=0.01, write_cmdline=False):
7374
"""Run a command, read stdout and stderr, prefix with timestamp.
7475
7576
The returned runtime contains a merged stdout+stderr log with timestamps
@@ -100,6 +101,9 @@ def run_command(runtime, output=None, timeout=0.01):
100101
errfile = os.path.join(runtime.cwd, "stderr.nipype")
101102
stderr = open(errfile, "wb")
102103

104+
if write_cmdline:
105+
(Path(runtime.cwd) / "command.txt").write_text(cmdline)
106+
103107
proc = Popen(
104108
cmdline,
105109
stdout=stdout,

0 commit comments

Comments
 (0)