Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion pydra/engine/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,33 @@ def set_state(self, splitter, combiner=None):

@property
def output_names(self):
"""Get the names of the outputs generated by the task."""
"""Get the names of the outputs from the task's output_spec
(not everything has to be generated, see generated_output_names).
"""
return [f.name for f in attr.fields(make_klass(self.output_spec))]

@property
def generated_output_names(self):
""" Get the names of the outputs generated by the task.
If the spec doesn't have generated_output_names method,
it uses output_names.
The results depends on the input provided to the task
"""
output_klass = make_klass(self.output_spec)
if hasattr(output_klass, "generated_output_names"):
output = output_klass(**{f.name: None for f in attr.fields(output_klass)})
# using updated input (after filing the templates)
_inputs = deepcopy(self.inputs)
modified_inputs = template_update(_inputs, self.output_dir)
if modified_inputs:
_inputs = attr.evolve(_inputs, **modified_inputs)

return output.generated_output_names(
inputs=_inputs, output_dir=self.output_dir
)
else:
return self.output_names

@property
def can_resume(self):
"""Whether the task accepts checkpoint-restart."""
Expand Down
2 changes: 1 addition & 1 deletion pydra/engine/helpers_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ def template_update(inputs, output_dir, map_copyfiles=None):
if fld.type not in [str, ty.Union[str, bool]]:
raise Exception(
f"fields with output_file_template"
"has to be a string or Union[str, bool]"
" has to be a string or Union[str, bool]"
)
dict_[fld.name] = template_update_single(
field=fld, inputs_dict=dict_, output_dir=output_dir
Expand Down
32 changes: 31 additions & 1 deletion pydra/engine/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ def collect_additional_outputs(self, inputs, output_dir):
raise AttributeError(
"File has to have default value or metadata"
)
elif not fld.default == attr.NOTHING:
elif fld.default != attr.NOTHING:
additional_out[fld.name] = self._field_defaultvalue(
fld, output_dir
)
Expand All @@ -420,6 +420,36 @@ def collect_additional_outputs(self, inputs, output_dir):
raise Exception("not implemented (collect_additional_output)")
return additional_out

def generated_output_names(self, inputs, output_dir):
""" Returns a list of all outputs that will be generated by the task.
Takes into account the task input and the requires list for the output fields.
TODO: should be in all Output specs?
"""
# checking the input (if all mandatory fields are provided, etc.)
inputs.check_fields_input_spec()
output_names = ["return_code", "stdout", "stderr"]
for fld in attr_fields(self):
if fld.name not in ["return_code", "stdout", "stderr"]:
if fld.type is File:
# assuming that field should have either default or metadata, but not both
if (
fld.default is None or fld.default == attr.NOTHING
) and not fld.metadata: # TODO: is it right?
raise AttributeError(
"File has to have default value or metadata"
)
elif fld.default != attr.NOTHING:
output_names.append(fld.name)
elif (
fld.metadata
and self._field_metadata(fld, inputs, output_dir)
!= attr.NOTHING
):
output_names.append(fld.name)
else:
raise Exception("not implemented (collect_additional_output)")
return output_names

def _field_defaultvalue(self, fld, output_dir):
"""Collect output file if the default value specified."""
if not isinstance(fld.default, (str, Path)):
Expand Down
40 changes: 40 additions & 0 deletions pydra/engine/tests/test_shelltask.py
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,12 @@ def test_shell_cmd_inputspec_outputspec_2():
)
shelly.inputs.file1 = "new_file_1.txt"
shelly.inputs.file2 = "new_file_2.txt"
# all fileds from output_spec should be in output_names and generated_output_names
assert (
shelly.output_names
== shelly.generated_output_names
== ["return_code", "stdout", "stderr", "newfile1", "newfile2"]
)

res = shelly()
assert res.output.stdout == ""
Expand Down Expand Up @@ -2714,6 +2720,20 @@ def test_shell_cmd_inputspec_outputspec_2a():
output_spec=my_output_spec,
)
shelly.inputs.file1 = "new_file_1.txt"
# generated_output_names shoule know that newfile2 will not be generated
assert shelly.output_names == [
"return_code",
"stdout",
"stderr",
"newfile1",
"newfile2",
]
assert shelly.generated_output_names == [
"return_code",
"stdout",
"stderr",
"newfile1",
]

res = shelly()
assert res.output.stdout == ""
Expand Down Expand Up @@ -2834,6 +2854,20 @@ def test_shell_cmd_inputspec_outputspec_3a():
)
shelly.inputs.file1 = "new_file_1.txt"
shelly.inputs.file2 = "new_file_2.txt"
# generated_output_names shoule know that newfile2 will not be generated
assert shelly.output_names == [
"return_code",
"stdout",
"stderr",
"newfile1",
"newfile2",
]
assert shelly.generated_output_names == [
"return_code",
"stdout",
"stderr",
"newfile1",
]

res = shelly()
assert res.output.stdout == ""
Expand Down Expand Up @@ -2884,6 +2918,12 @@ def test_shell_cmd_inputspec_outputspec_4():
)
shelly.inputs.file1 = "new_file_1.txt"
shelly.inputs.additional_inp = 2
# generated_output_names should be the same as output_names
assert (
shelly.output_names
== shelly.generated_output_names
== ["return_code", "stdout", "stderr", "newfile1"]
)

res = shelly()
assert res.output.stdout == ""
Expand Down