Skip to content

Commit 8a29e1e

Browse files
committed
debugging optional shell outputs
1 parent 9344e1c commit 8a29e1e

File tree

4 files changed

+52
-37
lines changed

4 files changed

+52
-37
lines changed

pydra/design/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,9 +715,9 @@ def make_converter(
715715
field_type, label=checker_label, superclass_auto_cast=True
716716
)
717717
converters = []
718-
if field.type in (MultiInputObj, MultiInputFile):
718+
if field_type in (MultiInputObj, MultiInputFile):
719719
converters.append(ensure_list)
720-
elif field.type in (MultiOutputObj, MultiOutputFile):
720+
elif field_type in (MultiOutputObj, MultiOutputFile):
721721
converters.append(from_list_if_single)
722722
if field.converter:
723723
converters.append(field.converter)

pydra/engine/helpers_file.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,11 @@ def _single_template_formatting(
334334
formatted_value = _element_formatting(
335335
template, val_dict, file_template, keep_extension=field.keep_extension
336336
)
337-
return Path(formatted_value) if formatted_value is not None else formatted_value
337+
if isinstance(formatted_value, list):
338+
return [Path(val) for val in formatted_value]
339+
elif isinstance(formatted_value, str):
340+
return Path(formatted_value)
341+
return None
338342

339343

340344
def _element_formatting(

pydra/engine/specs.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@
3232
from . import helpers_state as hlpst
3333
from . import lazy
3434
from pydra.utils.hash import hash_function, Cache
35-
from pydra.utils.typing import StateArray, is_multi_input
35+
from pydra.utils.typing import (
36+
StateArray,
37+
is_multi_input,
38+
MultiOutputObj,
39+
MultiOutputFile,
40+
)
3641
from pydra.design.base import Field, Arg, Out, RequirementSet, NO_DEFAULT
3742
from pydra.design import shell
3843

@@ -846,7 +851,17 @@ def _from_task(cls, task: "Task[ShellDef]") -> Self:
846851
else:
847852
resolved_value = cls._resolve_value(fld, task)
848853
# Set the resolved value
849-
setattr(outputs, fld.name, resolved_value)
854+
try:
855+
setattr(outputs, fld.name, resolved_value)
856+
except FileNotFoundError as e:
857+
if is_optional(fld.type):
858+
setattr(outputs, fld.name, None)
859+
else:
860+
e.add_note(
861+
f"file system path provided to {fld.name!r}, {resolved_value}, "
862+
f"does not exist, this is likely due to an error in the task {task}"
863+
)
864+
raise
850865
return outputs
851866

852867
@classmethod
@@ -1129,7 +1144,7 @@ def _command_pos_args(
11291144
# if False, nothing is added to the command.
11301145
if value is True:
11311146
cmd_add.append(field.argstr)
1132-
elif is_multi_input(tp):
1147+
elif is_multi_input(tp) or tp is MultiOutputObj or tp is MultiOutputFile:
11331148
# if the field is MultiInputObj, it is used to create a list of arguments
11341149
for val in value or []:
11351150
cmd_add += self._format_arg(field, val)

pydra/engine/tests/test_shelltask.py

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3062,7 +3062,7 @@ def no_fsl():
30623062

30633063

30643064
@pytest.mark.skipif(no_fsl(), reason="fsl is not installed")
3065-
def test_fsl(data_tests_dir):
3065+
def test_fsl(data_tests_dir, tmp_path):
30663066
"""mandatory field added to fields, value provided"""
30673067

30683068
_xor_inputs = [
@@ -3232,7 +3232,7 @@ class Shelly(ShellDef["Shelly.Outputs"]):
32323232
input: File = shell.arg(argstr="", help="input file")
32333233

32343234
class Outputs(ShellOutputs):
3235-
output: File = shell.outarg(
3235+
output: File | None = shell.outarg(
32363236
argstr="",
32373237
path_template="out.txt",
32383238
help="dummy output",
@@ -3252,7 +3252,7 @@ class Outputs(ShellOutputs):
32523252

32533253
def test_shell_cmd_non_existing_outputs_1(tmp_path):
32543254
"""Checking that non existing output files do not return a phantom path,
3255-
but return NOTHING instead"""
3255+
but return None instead"""
32563256

32573257
@shell.define
32583258
class Shelly(ShellDef["Shelly.Outputs"]):
@@ -3264,25 +3264,24 @@ class Shelly(ShellDef["Shelly.Outputs"]):
32643264
)
32653265

32663266
class Outputs(ShellOutputs):
3267-
out_1: File = shell.outarg(
3267+
out_1: File | None = shell.out(
32683268
help="fictional output #1",
3269-
path_template="{out_name}_1.nii",
3269+
callable=lambda: "out_1.nii",
32703270
)
3271-
out_2: File = shell.outarg(
3271+
out_2: File | None = shell.out(
32723272
help="fictional output #2",
3273-
path_template="{out_name}_2.nii",
3273+
callable=lambda: "out_2.nii",
32743274
)
32753275

3276-
shelly = Shelly(
3277-
out_name="test",
3278-
)
3279-
outputs = shelly()
3280-
assert outputs.out_1 == attr.NOTHING and outputs.out_2 == attr.NOTHING
3276+
shelly = Shelly(out_name="test")
3277+
outputs = shelly(cache_dir=tmp_path)
3278+
assert outputs.out_1 is None
3279+
assert outputs.out_2 is None
32813280

32823281

32833282
def test_shell_cmd_non_existing_outputs_2(tmp_path):
32843283
"""Checking that non existing output files do not return a phantom path,
3285-
but return NOTHING instead. This test has one existing and one non existing output file.
3284+
but return None instead. This test has one existing and one non existing output file.
32863285
"""
32873286

32883287
@shell.define
@@ -3306,7 +3305,7 @@ class Outputs(ShellOutputs):
33063305
)
33073306

33083307
shelly = Shelly(out_name="test")
3309-
outputs = shelly()
3308+
outputs = shelly(cache_dir=tmp_path)
33103309
# the first output file is created
33113310
assert outputs.out_1.fspath == next(tmp_path.iterdir()) / "test_1.nii"
33123311
assert outputs.out_1.fspath.exists()
@@ -3316,7 +3315,8 @@ class Outputs(ShellOutputs):
33163315

33173316
def test_shell_cmd_non_existing_outputs_3(tmp_path):
33183317
"""Checking that non existing output files do not return a phantom path,
3319-
but return NOTHING instead. This test has an existing mandatory output and another non existing output file.
3318+
but return None instead. This test has an existing mandatory output and another
3319+
non existing output file.
33203320
"""
33213321

33223322
@shell.define
@@ -3331,55 +3331,51 @@ class Shelly(ShellDef["Shelly.Outputs"]):
33313331

33323332
class Outputs(ShellOutputs):
33333333
out_1: File = shell.outarg(
3334-
help="fictional output #1",
3335-
path_template="{out_name}_1.nii",
3334+
help="real output #1",
3335+
default="{out_name}_1.nii",
33363336
)
3337-
out_2: File = shell.outarg(
3337+
out_2: File | None = shell.outarg(
33383338
help="fictional output #2",
3339-
path_template="{out_name}_2.nii",
3339+
default="{out_name}_2.nii",
33403340
)
33413341

33423342
shelly = Shelly(out_name="test")
33433343

3344-
outputs = shelly()
3344+
outputs = shelly(cache_dir=tmp_path)
33453345
# the first output file is created
33463346
assert outputs.out_1.fspath == next(tmp_path.iterdir()) / "test_1.nii"
33473347
assert outputs.out_1.fspath.exists()
33483348
# the second output file is not created
3349-
assert outputs.out_2 == attr.NOTHING
3349+
assert outputs.out_2 is None
33503350

33513351

33523352
def test_shell_cmd_non_existing_outputs_4(tmp_path):
33533353
"""Checking that non existing output files do not return a phantom path,
3354-
but return NOTHING instead. This test has an existing mandatory output and another non existing
3354+
but return None instead. This test has an existing mandatory output and another non existing
33553355
mandatory output file."""
33563356

33573357
@shell.define
33583358
class Shelly(ShellDef["Shelly.Outputs"]):
33593359
executable = "touch"
33603360
out_name: str = shell.arg(
3361-
help="""
3362-
base name of the pretend outputs.
3363-
""",
3361+
help="""base name of the pretend outputs.""",
33643362
argstr="{out_name}_1.nii",
33653363
)
33663364

33673365
class Outputs(ShellOutputs):
33683366
out_1: File = shell.outarg(
33693367
help="fictional output #1",
3370-
path_template="{out_name}_1.nii",
3368+
default="{out_name}_1.nii",
33713369
)
33723370
out_2: File = shell.outarg(
33733371
help="fictional output #2",
3374-
path_template="{out_name}_2.nii",
3372+
default="{out_name}_2.nii",
33753373
)
33763374

3377-
shelly = Shelly(
3378-
out_name="test",
3379-
)
3375+
shelly = Shelly(out_name="test")
33803376
# An exception should be raised because the second mandatory output does not exist
33813377
with pytest.raises(Exception) as excinfo:
3382-
shelly()
3378+
shelly(cache_dir=tmp_path)
33833379
assert "mandatory output for variable out_2 does not exist" == str(excinfo.value)
33843380
# checking if the first output was created
33853381
assert (next(tmp_path.iterdir()) / "test_1.nii").exists()

0 commit comments

Comments
 (0)