Skip to content

Commit 3cb45ee

Browse files
authored
Merge pull request #289 from djarecka/mnt/input_spec
extending command_args to include more flexible argstr, and sep
2 parents 2afb62f + d8957c2 commit 3cb45ee

File tree

10 files changed

+1895
-109
lines changed

10 files changed

+1895
-109
lines changed

pydra/engine/helpers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,41 @@ def load_task(task_pkl, ind=None):
598598
task.inputs = attr.evolve(task.inputs, **inputs_dict)
599599
task.state = None
600600
return task
601+
602+
603+
def position_adjustment(pos_args):
604+
"""
605+
sorting elements with the first element - position,
606+
the negative positions should go to the end of the list
607+
everything that has no position (i.e. it's None),
608+
should go between elements with positive positions an with negative pos.
609+
Returns a list of sorted args.
610+
"""
611+
# sorting all elements of the command
612+
try:
613+
pos_args.sort()
614+
except TypeError: # if some positions are None
615+
pos_args_none = []
616+
pos_args_int = []
617+
for el in pos_args:
618+
if el[0] is None:
619+
pos_args_none.append(el)
620+
else:
621+
pos_args_int.append(el)
622+
pos_args_int.sort()
623+
last_el = pos_args_int[-1][0]
624+
for el_none in pos_args_none:
625+
last_el += 1
626+
pos_args_int.append((last_el, el_none[1]))
627+
pos_args = pos_args_int
628+
629+
# if args available, they should be moved at the of the list
630+
while pos_args[0][0] < 0:
631+
pos_args.append(pos_args.pop(0))
632+
633+
# dropping the position index
634+
cmd_args = []
635+
for el in pos_args:
636+
cmd_args += el[1]
637+
638+
return cmd_args

pydra/engine/helpers_file.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from builtins import str, bytes, open
1111
import logging
1212
from pathlib import Path
13+
import typing as ty
1314

1415
related_filetype_sets = [(".hdr", ".img", ".mat"), (".nii", ".mat"), (".BRIK", ".HEAD")]
1516
"""List of neuroimaging file types that are to be interpreted together."""
@@ -512,23 +513,54 @@ def template_update(inputs, map_copyfiles=None):
512513

513514
from .specs import attr_fields
514515

515-
fields = attr_fields(inputs)
516-
# TODO: Create a dependency graph first and then traverse it
517-
for fld in fields:
518-
if getattr(inputs, fld.name) is not attr.NOTHING:
519-
continue
520-
if fld.metadata.get("output_file_template"):
521-
if fld.type is str:
522-
value = fld.metadata["output_file_template"].format(**dict_)
523-
dict_[fld.name] = str(value)
524-
else:
525-
raise Exception(
526-
f"output_file_template metadata for "
527-
"{fld.name} should be a string"
528-
)
516+
fields_templ = [
517+
fld for fld in attr_fields(inputs) if fld.metadata.get("output_file_template")
518+
]
519+
for fld in fields_templ:
520+
if fld.type not in [str, ty.Union[str, bool]]:
521+
raise Exception(
522+
f"fields with output_file_template"
523+
"has to be a string or Union[str, bool]"
524+
)
525+
inp_val_set = getattr(inputs, fld.name)
526+
if inp_val_set is not attr.NOTHING and not isinstance(inp_val_set, (str, bool)):
527+
raise Exception(f"{fld.name} has to be str or bool, but {inp_val_set} set")
528+
if isinstance(inp_val_set, bool) and fld.type is str:
529+
raise Exception(
530+
f"type of {fld.name} is str, consider using Union[str, bool]"
531+
)
532+
533+
if isinstance(inp_val_set, str):
534+
dict_[fld.name] = inp_val_set
535+
elif inp_val_set is False:
536+
# if False, the field should not be used, so setting attr.NOTHING
537+
dict_[fld.name] = attr.NOTHING
538+
else: # True or attr.NOTHING
539+
template = fld.metadata["output_file_template"]
540+
value = template.format(**dict_)
541+
value = removing_nothing(value)
542+
dict_[fld.name] = value
529543
return {k: v for k, v in dict_.items() if getattr(inputs, k) is not v}
530544

531545

546+
def removing_nothing(template_str):
547+
""" removing all fields that had NOTHING"""
548+
if "NOTHING" not in template_str:
549+
return template_str
550+
regex = re.compile("[^a-zA-Z_\-]")
551+
fields_str = regex.sub(" ", template_str)
552+
for fld in fields_str.split():
553+
if "NOTHING" in fld:
554+
template_str = template_str.replace(fld, "")
555+
return (
556+
template_str.replace("[ ", "[")
557+
.replace(" ]", "]")
558+
.replace(",]", "]")
559+
.replace("[,", "[")
560+
.strip()
561+
)
562+
563+
532564
def is_local_file(f):
533565
from .specs import File
534566

pydra/engine/specs.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,14 @@ def check_metadata(self):
207207
"copyfile",
208208
"help_string",
209209
"mandatory",
210+
"readonly",
210211
"output_field_name",
211212
"output_file_template",
212213
"position",
213214
"requires",
214215
"separate_ext",
215216
"xor",
217+
"sep",
216218
}
217219
# special inputs, don't have to follow rules for standard inputs
218220
special_input = ["_func", "_graph_checksums"]
@@ -222,22 +224,23 @@ def check_metadata(self):
222224
mdata = fld.metadata
223225
# checking keys from metadata
224226
if set(mdata.keys()) - supported_keys:
225-
raise Exception(
227+
raise AttributeError(
226228
f"only these keys are supported {supported_keys}, but "
227229
f"{set(mdata.keys()) - supported_keys} provided"
228230
)
229231
# checking if the help string is provided (required field)
230232
if "help_string" not in mdata:
231-
raise Exception(f"{fld.name} doesn't have help_string field")
232-
233+
raise AttributeError(f"{fld.name} doesn't have help_string field")
233234
# assuming that fields with output_file_template shouldn't have default
234-
if not fld.default == attr.NOTHING and mdata.get("output_file_template"):
235-
raise Exception(
235+
if fld.default not in [attr.NOTHING, True, False] and mdata.get(
236+
"output_file_template"
237+
):
238+
raise AttributeError(
236239
"default value should not be set together with output_file_template"
237240
)
238241
# not allowing for default if the field is mandatory
239242
if not fld.default == attr.NOTHING and mdata.get("mandatory"):
240-
raise Exception(
243+
raise AttributeError(
241244
"default value should not be set when the field is mandatory"
242245
)
243246
# setting default if value not provided and default is available
@@ -260,15 +263,17 @@ def check_fields_input_spec(self):
260263
# checking if the mandatory field is provided
261264
if getattr(self, fld.name) is attr.NOTHING:
262265
if mdata.get("mandatory"):
263-
raise Exception(f"{fld.name} is mandatory, but no value provided")
266+
raise AttributeError(
267+
f"{fld.name} is mandatory, but no value provided"
268+
)
264269
else:
265270
continue
266271
names.append(fld.name)
267272

268273
# checking if fields meet the xor and requires are
269274
if "xor" in mdata:
270275
if [el for el in mdata["xor"] if el in names]:
271-
raise Exception(
276+
raise AttributeError(
272277
f"{fld.name} is mutually exclusive with {mdata['xor']}"
273278
)
274279

@@ -283,15 +288,15 @@ def check_fields_input_spec(self):
283288
for nm, required in require_to_check.items():
284289
required_notfound = [el for el in required if el not in names]
285290
if required_notfound:
286-
raise Exception(f"{nm} requires {required_notfound}")
291+
raise AttributeError(f"{nm} requires {required_notfound}")
287292

288293
# TODO: types might be checked here
289294
self._type_checking()
290295

291296
def _file_check(self, field):
292297
file = Path(getattr(self, field.name))
293298
if not file.exists():
294-
raise Exception(f"the file from the {field.name} input does not exist")
299+
raise AttributeError(f"the file from the {field.name} input does not exist")
295300

296301
def _type_checking(self):
297302
"""Use fld.type to check the types TODO.
@@ -327,7 +332,9 @@ def collect_additional_outputs(self, input_spec, inputs, output_dir):
327332
if (
328333
fld.default is None or fld.default == attr.NOTHING
329334
) and not fld.metadata: # TODO: is it right?
330-
raise Exception("File has to have default value or metadata")
335+
raise AttributeError(
336+
"File has to have default value or metadata"
337+
)
331338
elif not fld.default == attr.NOTHING:
332339
additional_out[fld.name] = self._field_defaultvalue(
333340
fld, output_dir
@@ -343,7 +350,7 @@ def collect_additional_outputs(self, input_spec, inputs, output_dir):
343350
def _field_defaultvalue(self, fld, output_dir):
344351
"""Collect output file if the default value specified."""
345352
if not isinstance(fld.default, (str, Path)):
346-
raise Exception(
353+
raise AttributeError(
347354
f"{fld.name} is a File, so default value "
348355
f"should be a string or a Path, "
349356
f"{fld.default} provided"
@@ -358,15 +365,15 @@ def _field_defaultvalue(self, fld, output_dir):
358365
if default.exists():
359366
return default
360367
else:
361-
raise Exception(f"file {default} does not exist")
368+
raise AttributeError(f"file {default} does not exist")
362369
else:
363370
all_files = list(Path(default.parent).expanduser().glob(default.name))
364371
if len(all_files) > 1:
365372
return all_files
366373
elif len(all_files) == 1:
367374
return all_files[0]
368375
else:
369-
raise Exception(f"no file matches {default.name}")
376+
raise AttributeError(f"no file matches {default.name}")
370377

371378
def _field_metadata(self, fld, inputs, output_dir):
372379
"""Collect output file if metadata specified."""

0 commit comments

Comments
 (0)