From 7244d9c9c1b599e4adb1e70515f5f3c92cd8e4f4 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 22 Jun 2021 18:03:22 +0200 Subject: [PATCH 01/10] ENH: Simplify interface execution of ``Node`` --- nipype/interfaces/base/core.py | 4 +- nipype/interfaces/base/support.py | 11 ++++ nipype/pipeline/engine/nodes.py | 95 ++++++++++++------------------- 3 files changed, 48 insertions(+), 62 deletions(-) diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index e329b9e257..65b35cadce 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -723,6 +723,7 @@ def _run_interface(self, runtime, correct_return_codes=(0,)): runtime.stderr = None runtime.cmdline = self.cmdline runtime.environ.update(out_environ) + runtime.success_codes = correct_return_codes # which $cmd executable_name = shlex.split(self._cmd_prefix + self.cmd)[0] @@ -742,9 +743,6 @@ def _run_interface(self, runtime, correct_return_codes=(0,)): else "" ) runtime = run_command(runtime, output=self.terminal_output) - if runtime.returncode is None or runtime.returncode not in correct_return_codes: - self.raise_exception(runtime) - return runtime def _format_arg(self, name, trait_spec, value): diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index 58b384d75d..f00ecefed0 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -110,6 +110,17 @@ def __exit__(self, exc_type, exc_value, exc_tb): if self._ignore_exc: return True + _exitcode = ( + getattr(self._runtime, "returncode", None) + if getattr(self._runtime, "cmdline", None) + else 0 + ) + _success_codes = getattr(self._runtime, "success_codes", (0,)) + if _exitcode not in _success_codes: + self._runtime.traceback = ( + f"RuntimeError: subprocess exited with code {self._runtime.returncode}." + ) + @property def runtime(self): return self._runtime diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index b458b3f820..6f856b887a 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -9,6 +9,7 @@ import os import os.path as op +from pathlib import Path import shutil import socket from copy import deepcopy @@ -30,7 +31,6 @@ load_json, emptydirs, savepkl, - indirectory, silentrm, ) @@ -98,7 +98,7 @@ def __init__( run_without_submitting=False, n_procs=None, mem_gb=0.20, - **kwargs + **kwargs, ): """ Parameters @@ -697,72 +697,44 @@ def _run_command(self, execute, copyfiles=True): ) return result - outdir = self.output_dir() - # Run command: either execute is true or load_results failed. - result = InterfaceResult( - interface=self._interface.__class__, - runtime=Bunch( - cwd=outdir, - returncode=1, - environ=dict(os.environ), - hostname=socket.gethostname(), - ), - inputs=self._interface.inputs.get_traitsfree(), - ) - + outdir = Path(self.output_dir()) if copyfiles: self._originputs = deepcopy(self._interface.inputs) self._copyfiles_to_wd(execute=execute) - message = '[Node] Running "{}" ("{}.{}")'.format( - self.name, self._interface.__module__, self._interface.__class__.__name__ + # Run command: either execute is true or load_results failed. + logger.info( + f'[Node] Executing "{self.name}" <{self._interface.__module__}' + f".{self._interface.__class__.__name__}>" + ) + # Invoke core run method of the interface ignoring exceptions + result = self._interface.run(cwd=outdir, ignore_exception=True) + logger.info( + f'[Node] Finished "{self.name}", elapsed time {result.runtime.duration}s.' ) + if issubclass(self._interface.__class__, CommandLine): - try: - with indirectory(outdir): - cmd = self._interface.cmdline - except Exception as msg: - result.runtime.stderr = "{}\n\n{}".format( - getattr(result.runtime, "stderr", ""), msg - ) - _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: - print(cmd + "\n", file=fd) - message += ", a CommandLine Interface with command:\n{}".format(cmd) - logger.info(message) - try: - result = self._interface.run(cwd=outdir) - except Exception as msg: - result.runtime.stderr = "%s\n\n%s".format( - getattr(result.runtime, "stderr", ""), msg - ) - _save_resultfile( - result, + # Write out command line as it happened + (outdir / "command.txt").write_text(f"{result.runtime.cmdline}\n") + + exc_tb = getattr(result, "traceback", None) + + if not exc_tb: + # Clean working directory if no errors + dirs2keep = None + if isinstance(self, MapNode): + dirs2keep = [op.join(outdir, "mapflow")] + + result.outputs = clean_working_directory( + result.outputs, outdir, - self.name, - rebase=str2bool(self.config["execution"]["use_relative_paths"]), + self._interface.inputs, + self.needed_outputs, + self.config, + dirs2keep=dirs2keep, ) - raise - - dirs2keep = None - if isinstance(self, MapNode): - dirs2keep = [op.join(outdir, "mapflow")] - result.outputs = clean_working_directory( - result.outputs, - outdir, - self._interface.inputs, - self.needed_outputs, - self.config, - dirs2keep=dirs2keep, - ) + # Store results file under all circumstances _save_resultfile( result, outdir, @@ -770,6 +742,11 @@ def _run_command(self, execute, copyfiles=True): rebase=str2bool(self.config["execution"]["use_relative_paths"]), ) + if exc_tb: + raise RuntimeError( + f"Exception raised while executing Node {self.name}.\n\n{result.runtime.traceback}" + ) + return result def _copyfiles_to_wd(self, execute=True, linksonly=False): From 58c90c4424cd1c7fc8991bb8920a0dbe5078d71f Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 24 Jun 2021 17:07:27 +0200 Subject: [PATCH 02/10] enh: better error handling --- .../interfaces/utility/tests/test_wrappers.py | 2 +- nipype/pipeline/engine/nodes.py | 12 ++++++++---- nipype/pipeline/engine/tests/test_utils.py | 2 +- nipype/pipeline/plugins/base.py | 12 +++++++++++- nipype/pipeline/plugins/linear.py | 17 ++++++++++------- nipype/pipeline/plugins/tests/test_sgelike.py | 9 ++++++--- nipype/pipeline/plugins/tools.py | 3 --- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/nipype/interfaces/utility/tests/test_wrappers.py b/nipype/interfaces/utility/tests/test_wrappers.py index 76413e5760..fda81b2f5b 100644 --- a/nipype/interfaces/utility/tests/test_wrappers.py +++ b/nipype/interfaces/utility/tests/test_wrappers.py @@ -73,7 +73,7 @@ def should_fail(tmp): def test_should_fail(tmpdir): - with pytest.raises(NameError): + with pytest.raises(pe.nodes.NodeExecutionError): should_fail(tmpdir) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 6f856b887a..378f88957c 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -64,6 +64,10 @@ logger = logging.getLogger("nipype.workflow") +class NodeExecutionError(RuntimeError): + """A nipype-specific name for exceptions when executing a Node.""" + + class Node(EngineBase): """ Wraps interface objects for use in pipeline @@ -582,7 +586,7 @@ def _get_inputs(self): logger.critical("%s", e) if outputs is None: - raise RuntimeError( + raise NodeExecutionError( """\ Error populating the inputs of node "%s": the results file of the source node \ (%s) does not contain any outputs.""" @@ -717,7 +721,7 @@ def _run_command(self, execute, copyfiles=True): # Write out command line as it happened (outdir / "command.txt").write_text(f"{result.runtime.cmdline}\n") - exc_tb = getattr(result, "traceback", None) + exc_tb = getattr(result.runtime, "traceback", None) if not exc_tb: # Clean working directory if no errors @@ -743,7 +747,7 @@ def _run_command(self, execute, copyfiles=True): ) if exc_tb: - raise RuntimeError( + raise NodeExecutionError( f"Exception raised while executing Node {self.name}.\n\n{result.runtime.traceback}" ) @@ -1267,7 +1271,7 @@ def _collate_results(self, nodes): if code is not None: msg += ["Subnode %d failed" % i] msg += ["Error: %s" % str(code)] - raise Exception( + raise NodeExecutionError( "Subnodes of node: %s failed:\n%s" % (self.name, "\n".join(msg)) ) diff --git a/nipype/pipeline/engine/tests/test_utils.py b/nipype/pipeline/engine/tests/test_utils.py index 0705f0ad53..07b01bd3ba 100644 --- a/nipype/pipeline/engine/tests/test_utils.py +++ b/nipype/pipeline/engine/tests/test_utils.py @@ -183,7 +183,7 @@ def test_mapnode_crash(tmpdir): node.config = deepcopy(config._sections) node.config["execution"]["stop_on_first_crash"] = True node.base_dir = tmpdir.strpath - with pytest.raises(TypeError): + with pytest.raises(pe.nodes.NodeExecutionError): node.run() os.chdir(cwd) diff --git a/nipype/pipeline/plugins/base.py b/nipype/pipeline/plugins/base.py index a33a40aab6..a68ac6957f 100644 --- a/nipype/pipeline/plugins/base.py +++ b/nipype/pipeline/plugins/base.py @@ -123,6 +123,7 @@ def run(self, graph, config, updatehash=False): self.mapnodesubids = {} # setup polling - TODO: change to threaded model notrun = [] + errors = [] old_progress_stats = None old_presub_stats = None @@ -155,14 +156,16 @@ def run(self, graph, config, updatehash=False): taskid, jobid = self.pending_tasks.pop() try: result = self._get_result(taskid) - except Exception: + except Exception as exc: notrun.append(self._clean_queue(jobid, graph)) + errors.append(exc) else: if result: if result["traceback"]: notrun.append( self._clean_queue(jobid, graph, result=result) ) + errors.append("".join(result["traceback"])) else: self._task_finished_cb(jobid) self._remove_node_dirs() @@ -194,6 +197,13 @@ def run(self, graph, config, updatehash=False): # close any open resources self._postrun_check() + if errors: + # If one or more nodes failed, re-rise first of them + if isinstance(errors[0], str): + raise RuntimeError(errors[0]) + + raise errors[0] + def _get_result(self, taskid): raise NotImplementedError diff --git a/nipype/pipeline/plugins/linear.py b/nipype/pipeline/plugins/linear.py index 8471a187d3..196e40cbd2 100644 --- a/nipype/pipeline/plugins/linear.py +++ b/nipype/pipeline/plugins/linear.py @@ -34,6 +34,8 @@ def run(self, graph, config, updatehash=False): old_wd = os.getcwd() notrun = [] donotrun = [] + stop_on_first_crash = str2bool(config["execution"]["stop_on_first_crash"]) + errors = [] nodes, _ = topological_sort(graph) for node in nodes: endstatus = "end" @@ -43,13 +45,11 @@ def run(self, graph, config, updatehash=False): if self._status_callback: self._status_callback(node, "start") node.run(updatehash=updatehash) - except: + except Exception as exc: endstatus = "exception" # bare except, but i really don't know where a # node might fail crashfile = report_crash(node) - if str2bool(config["execution"]["stop_on_first_crash"]): - raise # remove dependencies from queue subnodes = [s for s in dfs_preorder(graph, node)] notrun.append( @@ -57,13 +57,16 @@ def run(self, graph, config, updatehash=False): ) donotrun.extend(subnodes) # Delay raising the crash until we cleaned the house - if str2bool(config["execution"]["stop_on_first_crash"]): - os.chdir(old_wd) # Return wherever we were before - report_nodes_not_run(notrun) # report before raising - raise + errors.append(exc) + + if stop_on_first_crash: + break finally: if self._status_callback: self._status_callback(node, endstatus) os.chdir(old_wd) # Return wherever we were before report_nodes_not_run(notrun) + if errors: + # Re-raise exception of first failed node + raise errors[0] diff --git a/nipype/pipeline/plugins/tests/test_sgelike.py b/nipype/pipeline/plugins/tests/test_sgelike.py index 9c7cdc1412..140150c9b0 100644 --- a/nipype/pipeline/plugins/tests/test_sgelike.py +++ b/nipype/pipeline/plugins/tests/test_sgelike.py @@ -29,7 +29,10 @@ def test_crashfile_creation(tmp_path): sgelike_plugin = SGELikeBatchManagerBase("") with pytest.raises(RuntimeError) as e: assert pipe.run(plugin=sgelike_plugin) - assert str(e.value) == "Workflow did not execute cleanly. Check log for details" + assert str(e.value) == "Workflow did not execute cleanly. Check log for details" - crashfiles = tmp_path.glob("crash*crasher*.pklz") - assert len(list(crashfiles)) == 1 + crashfiles = ( + list(tmp_path.glob("crash*crasher*.pklz")) + + list(tmp_path.glob("crash*crasher*.txt")) + ) + assert len(crashfiles) == 1 diff --git a/nipype/pipeline/plugins/tools.py b/nipype/pipeline/plugins/tools.py index b816e61463..86fdf67ac6 100644 --- a/nipype/pipeline/plugins/tools.py +++ b/nipype/pipeline/plugins/tools.py @@ -93,9 +93,6 @@ def report_nodes_not_run(notrun): for subnode in info["dependents"]: logger.debug(subnode._id) logger.info("***********************************") - raise RuntimeError( - ("Workflow did not execute cleanly. " "Check log for details") - ) def create_pyscript(node, updatehash=False, store_exception=True): From 135ce497a18adbe0811441c2b720910ec549aa6f Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 24 Jun 2021 17:43:22 +0200 Subject: [PATCH 03/10] sty: run black --- nipype/pipeline/plugins/tests/test_sgelike.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nipype/pipeline/plugins/tests/test_sgelike.py b/nipype/pipeline/plugins/tests/test_sgelike.py index 140150c9b0..65c49b9ba8 100644 --- a/nipype/pipeline/plugins/tests/test_sgelike.py +++ b/nipype/pipeline/plugins/tests/test_sgelike.py @@ -31,8 +31,7 @@ def test_crashfile_creation(tmp_path): assert pipe.run(plugin=sgelike_plugin) assert str(e.value) == "Workflow did not execute cleanly. Check log for details" - crashfiles = ( - list(tmp_path.glob("crash*crasher*.pklz")) - + list(tmp_path.glob("crash*crasher*.txt")) + crashfiles = list(tmp_path.glob("crash*crasher*.pklz")) + list( + tmp_path.glob("crash*crasher*.txt") ) assert len(crashfiles) == 1 From 21c0b2621504730bb104bd2613fbd758e66555d5 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 25 Jun 2021 11:05:56 +0200 Subject: [PATCH 04/10] fix: remove duplicate log entries --- nipype/pipeline/engine/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 378f88957c..1c1f89c102 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -443,7 +443,8 @@ def run(self, updatehash=False): ) # Check hash, check whether run should be enforced - logger.info('[Node] Setting-up "%s" in "%s".', self.fullname, outdir) + if not isinstance(self, MapNode): + logger.info(f'[Node] Setting-up "{self.fullname}" in "{outdir}".') cached, updated = self.is_cached() # If the node is cached, check on pklz files and finish @@ -534,7 +535,6 @@ def run(self, updatehash=False): # Tear-up after success shutil.move(hashfile_unfinished, hashfile_unfinished.replace("_unfinished", "")) write_node_report(self, result=result, is_mapnode=isinstance(self, MapNode)) - logger.info('[Node] Finished "%s".', self.fullname) return result def _get_hashval(self): From c208e7b261201a2695b95262c0853d55b1a60dd5 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 25 Jun 2021 11:07:12 +0200 Subject: [PATCH 05/10] fix: robust matlab installation check --- nipype/interfaces/matlab.py | 24 +++++------------------- nipype/interfaces/tests/test_matlab.py | 2 +- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/nipype/interfaces/matlab.py b/nipype/interfaces/matlab.py index 543b1e7a55..03e34b0b43 100644 --- a/nipype/interfaces/matlab.py +++ b/nipype/interfaces/matlab.py @@ -17,25 +17,11 @@ def get_matlab_command(): - if "NIPYPE_NO_MATLAB" in os.environ: - return None - - try: - matlab_cmd = os.environ["MATLABCMD"] - except: - matlab_cmd = "matlab" - - try: - res = CommandLine( - command="which", - args=matlab_cmd, - resource_monitor=False, - terminal_output="allatonce", - ).run() - matlab_path = res.runtime.stdout.strip() - except Exception: - return None - return matlab_cmd + """Determine whether Matlab is installed and can be executed.""" + if "NIPYPE_NO_MATLAB" not in os.environ: + from nipype.utils.filemanip import which + + return which(os.getenv("MATLABCMD", "matlab")) no_matlab = get_matlab_command() is None diff --git a/nipype/interfaces/tests/test_matlab.py b/nipype/interfaces/tests/test_matlab.py index 64f1de846f..21679a78e2 100644 --- a/nipype/interfaces/tests/test_matlab.py +++ b/nipype/interfaces/tests/test_matlab.py @@ -103,7 +103,7 @@ def test_run_interface(tmpdir): # bypasses ubuntu dash issue mc = mlab.MatlabCommand(script="foo;", paths=[tmpdir.strpath], mfile=True) assert not os.path.exists(default_script_file), "scriptfile should not exist 4." - with pytest.raises(RuntimeError): + with pytest.raises(OSError): mc.run() assert os.path.exists(default_script_file), "scriptfile should exist 4." if os.path.exists(default_script_file): # cleanup From a08638e2bb727d69b6814a87a15474fcefbdeeec Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Mon, 2 Aug 2021 17:43:03 +0200 Subject: [PATCH 06/10] Update nipype/pipeline/engine/nodes.py Co-authored-by: Chris Markiewicz --- nipype/pipeline/engine/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py index 1c1f89c102..bda77b1956 100644 --- a/nipype/pipeline/engine/nodes.py +++ b/nipype/pipeline/engine/nodes.py @@ -719,7 +719,7 @@ def _run_command(self, execute, copyfiles=True): if issubclass(self._interface.__class__, CommandLine): # Write out command line as it happened - (outdir / "command.txt").write_text(f"{result.runtime.cmdline}\n") + Path.write_text(outdir / "command.txt", f"{result.runtime.cmdline}\n") exc_tb = getattr(result.runtime, "traceback", None) From bbcd4ff1cc20564b92cc0e615f8443abbf95b28d Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Mon, 2 Aug 2021 17:45:34 +0200 Subject: [PATCH 07/10] Update nipype/interfaces/base/support.py Co-authored-by: Chris Markiewicz --- nipype/interfaces/base/support.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index f00ecefed0..8a90942feb 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -110,16 +110,10 @@ def __exit__(self, exc_type, exc_value, exc_tb): if self._ignore_exc: return True - _exitcode = ( - getattr(self._runtime, "returncode", None) - if getattr(self._runtime, "cmdline", None) - else 0 - ) - _success_codes = getattr(self._runtime, "success_codes", (0,)) - if _exitcode not in _success_codes: - self._runtime.traceback = ( - f"RuntimeError: subprocess exited with code {self._runtime.returncode}." - ) + if hasattr(self._runtime, "cmdline"): + retcode = self._runtime.returncode + if retcode not in self._runtime.success_codes: + self._runtime.traceback = f"RuntimeError: subprocess exited with code {retcode}." @property def runtime(self): From 1833a12f7b497a82bf4f1ff1c26a3351cc3c0b0e Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 28 Sep 2021 09:23:27 +0200 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Chris Markiewicz --- nipype/pipeline/plugins/base.py | 10 +++++++--- nipype/pipeline/plugins/tests/test_sgelike.py | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nipype/pipeline/plugins/base.py b/nipype/pipeline/plugins/base.py index a68ac6957f..89eb9ee275 100644 --- a/nipype/pipeline/plugins/base.py +++ b/nipype/pipeline/plugins/base.py @@ -199,10 +199,14 @@ def run(self, graph, config, updatehash=False): if errors: # If one or more nodes failed, re-rise first of them - if isinstance(errors[0], str): - raise RuntimeError(errors[0]) + error, cause = errors[0], None + if isinstance(error, str): + error = RuntimeError(error) - raise errors[0] + if len(errors) > 1: + error, cause = RuntimeError(f"{len(errors)} raised. Re-raising first."), error + + raise error from cause def _get_result(self, taskid): raise NotImplementedError diff --git a/nipype/pipeline/plugins/tests/test_sgelike.py b/nipype/pipeline/plugins/tests/test_sgelike.py index 65c49b9ba8..4c5807e262 100644 --- a/nipype/pipeline/plugins/tests/test_sgelike.py +++ b/nipype/pipeline/plugins/tests/test_sgelike.py @@ -29,7 +29,6 @@ def test_crashfile_creation(tmp_path): sgelike_plugin = SGELikeBatchManagerBase("") with pytest.raises(RuntimeError) as e: assert pipe.run(plugin=sgelike_plugin) - assert str(e.value) == "Workflow did not execute cleanly. Check log for details" crashfiles = list(tmp_path.glob("crash*crasher*.pklz")) + list( tmp_path.glob("crash*crasher*.txt") From bed65aadb7503d62b7f174f7ed643f35f162e6d3 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 29 Sep 2021 10:08:01 +0200 Subject: [PATCH 09/10] Update nipype/pipeline/plugins/linear.py Co-authored-by: Chris Markiewicz --- nipype/pipeline/plugins/linear.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nipype/pipeline/plugins/linear.py b/nipype/pipeline/plugins/linear.py index 196e40cbd2..ed62c06319 100644 --- a/nipype/pipeline/plugins/linear.py +++ b/nipype/pipeline/plugins/linear.py @@ -68,5 +68,12 @@ def run(self, graph, config, updatehash=False): os.chdir(old_wd) # Return wherever we were before report_nodes_not_run(notrun) if errors: - # Re-raise exception of first failed node - raise errors[0] + # If one or more nodes failed, re-rise first of them + error, cause = errors[0], None + if isinstance(error, str): + error = RuntimeError(error) + + if len(errors) > 1: + error, cause = RuntimeError(f"{len(errors)} raised. Re-raising first."), error + + raise error from cause From 5f280da629bb7b5dce908633d2deea85b55dd67b Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 29 Sep 2021 16:53:54 +0200 Subject: [PATCH 10/10] sty: run black on affected files --- nipype/interfaces/base/support.py | 4 +++- nipype/pipeline/plugins/base.py | 7 +++++-- nipype/pipeline/plugins/linear.py | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index 8a90942feb..175438b6d5 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -113,7 +113,9 @@ def __exit__(self, exc_type, exc_value, exc_tb): if hasattr(self._runtime, "cmdline"): retcode = self._runtime.returncode if retcode not in self._runtime.success_codes: - self._runtime.traceback = f"RuntimeError: subprocess exited with code {retcode}." + self._runtime.traceback = ( + f"RuntimeError: subprocess exited with code {retcode}." + ) @property def runtime(self): diff --git a/nipype/pipeline/plugins/base.py b/nipype/pipeline/plugins/base.py index 89eb9ee275..dbcf415b4e 100644 --- a/nipype/pipeline/plugins/base.py +++ b/nipype/pipeline/plugins/base.py @@ -147,7 +147,7 @@ def run(self, graph, config, updatehash=False): "Progress: %d jobs, %d/%d/%d " "(done/running/ready), %d/%d " "(pending_tasks/waiting).", - *progress_stats + *progress_stats, ) old_progress_stats = progress_stats toappend = [] @@ -204,7 +204,10 @@ def run(self, graph, config, updatehash=False): error = RuntimeError(error) if len(errors) > 1: - error, cause = RuntimeError(f"{len(errors)} raised. Re-raising first."), error + error, cause = ( + RuntimeError(f"{len(errors)} raised. Re-raising first."), + error, + ) raise error from cause diff --git a/nipype/pipeline/plugins/linear.py b/nipype/pipeline/plugins/linear.py index ed62c06319..8449e34111 100644 --- a/nipype/pipeline/plugins/linear.py +++ b/nipype/pipeline/plugins/linear.py @@ -74,6 +74,9 @@ def run(self, graph, config, updatehash=False): error = RuntimeError(error) if len(errors) > 1: - error, cause = RuntimeError(f"{len(errors)} raised. Re-raising first."), error + error, cause = ( + RuntimeError(f"{len(errors)} raised. Re-raising first."), + error, + ) raise error from cause