Skip to content

Commit

Permalink
Merge pull request #348 from djarecka/mnt/nipype_interf_convert
Browse files Browse the repository at this point in the history
[mnt, wip] ShellCommandTask updates (motivated my nipype interfaces)
  • Loading branch information
djarecka authored Sep 21, 2020
2 parents 7ea9477 + e469555 commit 8fba851
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 8 deletions.
6 changes: 4 additions & 2 deletions pydra/engine/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def check_fields_input_spec(self):

# checking if fields meet the xor and requires are
if "xor" in mdata:
if [el for el in mdata["xor"] if el in names]:
if [el for el in mdata["xor"] if (el in names and el != fld.name)]:
raise AttributeError(
f"{fld.name} is mutually exclusive with {mdata['xor']}"
)
Expand Down Expand Up @@ -473,6 +473,8 @@ def _check_requires(self, fld, inputs):
""" checking if all fields from the requires and template are set in the input
if requires is a list of list, checking if at least one list has all elements set
"""
from .helpers import ensure_list

if "requires" in fld.metadata:
# if requires is a list of list it is treated as el[0] OR el[1] OR...
if all([isinstance(el, list) for el in fld.metadata["requires"]]):
Expand Down Expand Up @@ -512,7 +514,7 @@ def _check_requires(self, fld, inputs):
required_found = False
break
elif isinstance(inp, tuple): # (name, allowed values)
inp, allowed_val = inp
inp, allowed_val = inp[0], ensure_list(inp[1])
if not hasattr(inputs, inp):
raise Exception(
f"{inp} is not a valid input field, can't be used in requires"
Expand Down
30 changes: 24 additions & 6 deletions pydra/engine/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import inspect
import typing as ty
from pathlib import Path
import warnings

from .core import TaskBase, is_lazy
from ..utils.messenger import AuditFlag
Expand Down Expand Up @@ -216,6 +217,9 @@ def _run_task(self):
class ShellCommandTask(TaskBase):
"""Wrap a shell command as a task element."""

input_spec = None
output_spec = None

def __new__(cls, container_info=None, *args, **kwargs):
if not container_info:
return super().__new__(cls)
Expand Down Expand Up @@ -275,15 +279,29 @@ def __init__(
TODO
"""
if input_spec is None:
input_spec = SpecInfo(name="Inputs", fields=[], bases=(ShellSpec,))
self.input_spec = input_spec
if output_spec is None:
output_spec = SpecInfo(name="Output", fields=[], bases=(ShellOutSpec,))

self.output_spec = output_spec
# using provided spec, class attribute or setting the default SpecInfo
self.input_spec = (
input_spec
or self.input_spec
or SpecInfo(name="Inputs", fields=[], bases=(ShellSpec,))
)
self.output_spec = (
output_spec
or self.output_spec
or SpecInfo(name="Output", fields=[], bases=(ShellOutSpec,))
)
self.output_spec = output_from_inputfields(self.output_spec, self.input_spec)

for special_inp in ["executable", "args"]:
if hasattr(self, special_inp):
if special_inp not in kwargs:
kwargs[special_inp] = getattr(self, special_inp)
elif kwargs[special_inp] != getattr(self, special_inp):
warnings.warn(
f"you are changing the executable from {getattr(self, special_inp)} to {kwargs[special_inp]}"
)

super().__init__(
name=name,
inputs=kwargs,
Expand Down
104 changes: 104 additions & 0 deletions pydra/engine/tests/test_nipype1_convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import attr
import typing as ty
import os, sys
import pytest
from pathlib import Path


from ..task import ShellCommandTask
from ..submitter import Submitter
from ..core import Workflow
from ..specs import ShellOutSpec, ShellSpec, SpecInfo, File
from .utils import result_no_submitter, result_submitter, use_validator

interf_input_spec = SpecInfo(
name="Input", fields=[("test", ty.Any, {"help_string": "test"})], bases=(ShellSpec,)
)
interf_output_spec = SpecInfo(
name="Output", fields=[("test_out", File, "*.txt")], bases=(ShellOutSpec,)
)


class Interf_1(ShellCommandTask):
"""class with customized input/output specs"""

input_spec = interf_input_spec
output_spec = interf_output_spec


class Interf_2(ShellCommandTask):
"""class with customized input/output specs and executables"""

input_spec = interf_input_spec
output_spec = interf_output_spec
executable = "testing command"


class TouchInterf(ShellCommandTask):
"""class with customized input and executables"""

input_spec = SpecInfo(
name="Input",
fields=[
(
"new_file",
str,
{
"help_string": "new_file",
"argstr": "",
"output_file_template": "{new_file}",
},
)
],
bases=(ShellSpec,),
)
executable = "touch"


def test_interface_specs_1():
"""testing if class input/output spec are set properly"""
task = Interf_1(executable="ls")
assert task.input_spec == interf_input_spec
assert task.output_spec == interf_output_spec


def test_interface_specs_2():
"""testing if class input/output spec are overwritten properly by the user's specs"""
my_input_spec = SpecInfo(
name="Input",
fields=[("my_inp", ty.Any, {"help_string": "my inp"})],
bases=(ShellSpec,),
)
my_output_spec = SpecInfo(
name="Output", fields=[("my_out", File, "*.txt")], bases=(ShellOutSpec,)
)
task = Interf_1(input_spec=my_input_spec, output_spec=my_output_spec)
assert task.input_spec == my_input_spec
assert task.output_spec == my_output_spec


def test_interface_executable_1():
"""testing if the class executable is properly set and used in the command line"""
task = Interf_2()
assert task.executable == "testing command"
assert task.inputs.executable == "testing command"
assert task.cmdline == "testing command"


def test_interface_executable_2():
"""testing if the class executable is overwritten by the user's input (and if the warning is raised)"""
# warning that the user changes the executable from the one that is set as a class attribute
with pytest.warns(UserWarning, match="changing the executable"):
task = Interf_2(executable="i want a different command")
assert task.executable == "testing command"
# task.executable stays the same, but input.executable is changed, so the cmd is changed
assert task.inputs.executable == "i want a different command"
assert task.cmdline == "i want a different command"


def test_interface_run_1():
"""testing execution of a simple interf with customized input and executable"""
task = TouchInterf(new_file="hello.txt")
assert task.cmdline == "touch hello.txt"
res = task()
assert res.output.new_file.exists()

0 comments on commit 8fba851

Please sign in to comment.