Skip to content

Commit 761d97a

Browse files
committed
test_dockertask tests pass
1 parent 95f7066 commit 761d97a

File tree

4 files changed

+149
-82
lines changed

4 files changed

+149
-82
lines changed

new-docs/source/tutorial/5-shell.ipynb

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,6 @@
109109
"print(trim_png.cmdline)"
110110
]
111111
},
112-
{
113-
"cell_type": "markdown",
114-
"metadata": {},
115-
"source": []
116-
},
117112
{
118113
"cell_type": "markdown",
119114
"metadata": {},
@@ -180,6 +175,68 @@
180175
"print(f\"'--int-arg' default: {fields_dict(Cp)['int_arg'].default}\")"
181176
]
182177
},
178+
{
179+
"cell_type": "markdown",
180+
"metadata": {},
181+
"source": [
182+
"## Path templates for output files\n",
183+
"\n",
184+
"By default, when an output file argument is defined, a `path_template` attribute will\n",
185+
"be assigned to the field based on its name and extension (if applicable). For example,\n",
186+
"the `zipped` output field in the following Gzip command will be assigned a\n",
187+
"`path_template` of `out_file.gz`"
188+
]
189+
},
190+
{
191+
"cell_type": "code",
192+
"execution_count": null,
193+
"metadata": {},
194+
"outputs": [],
195+
"source": [
196+
"from pydra.design import shell\n",
197+
"from fileformats.generic import File\n",
198+
"\n",
199+
"Gzip = shell.define(\"gzip <out|out_file:application/gzip> <in_files+>\")\n",
200+
"gzip = Gzip(in_files=File.mock(\"/a/file.txt\"))\n",
201+
"print(gzip.cmdline)"
202+
]
203+
},
204+
{
205+
"cell_type": "markdown",
206+
"metadata": {},
207+
"source": [
208+
"However, if this needs to be specified it can be by using the `$` operator, e.g."
209+
]
210+
},
211+
{
212+
"cell_type": "code",
213+
"execution_count": null,
214+
"metadata": {},
215+
"outputs": [],
216+
"source": [
217+
"Gzip = shell.define(\"gzip <out|out_file:application/gzip$zipped.gz> <in_files+>\")\n",
218+
"gzip = Gzip(in_files=File.mock(\"/a/file.txt\"))\n",
219+
"print(gzip.cmdline)"
220+
]
221+
},
222+
{
223+
"cell_type": "markdown",
224+
"metadata": {},
225+
"source": [
226+
"To give the field a path_template of `archive.gz` when it is written on the command line.\n",
227+
"Note that this value can always be overridden when the task is initialised, e.g."
228+
]
229+
},
230+
{
231+
"cell_type": "code",
232+
"execution_count": null,
233+
"metadata": {},
234+
"outputs": [],
235+
"source": [
236+
"gzip = Gzip(in_files=File.mock(\"/a/file.txt\"), out_file=\"/path/to/archive.gz\")\n",
237+
"print(gzip.cmdline)"
238+
]
239+
},
183240
{
184241
"cell_type": "markdown",
185242
"metadata": {},

pydra/design/shell.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ def parse_command_line_template(
537537
tokens = tokens[start_args_index:]
538538
if not tokens:
539539
return executable, inputs, outputs
540-
arg_pattern = r"<([:a-zA-Z0-9_,\|\-\.\/\+\*]+(?:\?|=[^>]+)?)>"
540+
arg_pattern = r"<([:a-zA-Z0-9_,\|\-\.\/\+\*]+(?:\?|(?:=|\$)[^>]+)?)>"
541541
opt_pattern = r"--?[a-zA-Z0-9_]+"
542542
arg_re = re.compile(arg_pattern)
543543
opt_re = re.compile(opt_pattern)
@@ -632,6 +632,13 @@ def from_type_str(type_str) -> type:
632632
elif "=" in name:
633633
name, default = name.split("=")
634634
kwds["default"] = eval(default)
635+
elif "$" in name:
636+
name, path_template = name.split("$")
637+
kwds["path_template"] = path_template
638+
if field_type is not outarg:
639+
raise ValueError(
640+
f"Path templates can only be used with output fields, not {token}"
641+
)
635642
if ":" in name:
636643
name, type_str = name.split(":")
637644
type_ = from_type_str(type_str)
@@ -649,7 +656,7 @@ def from_type_str(type_str) -> type:
649656
# Add field to outputs with the same name as the input
650657
add_arg(name, out, {"type": type_, "callable": _InputPassThrough(name)})
651658
# If name contains a '.', treat it as a file template and strip it from the name
652-
if field_type is outarg:
659+
if field_type is outarg and "path_template" not in kwds:
653660
path_template = name
654661
if is_fileset_or_union(type_):
655662
if ty.get_origin(type_):

pydra/engine/environments.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def bind(self, loc, mode="ro"):
9292
loc_abs = Path(loc).absolute()
9393
return f"{loc_abs}:{self.root}{loc_abs}:{mode}"
9494

95-
def _get_bindings(
95+
def get_bindings(
9696
self, task: "Task", root: str | None = None
9797
) -> tuple[dict[str, tuple[str, str]], dict[str, tuple[Path, ...]]]:
9898
"""Return bindings necessary to run task in an alternative root.
@@ -156,13 +156,14 @@ class Docker(Container):
156156
def execute(self, task: "Task[ShellDef]") -> dict[str, ty.Any]:
157157
docker_img = f"{self.image}:{self.tag}"
158158
# mounting all input locations
159-
mounts, input_updates = self._get_bindings(task=task, root=self.root)
159+
mounts, input_updates = self.get_bindings(task=task, root=self.root)
160+
161+
# add the cache directory to the list of mounts
162+
mounts[task.cache_dir] = (f"{self.root}{task.cache_dir}", "rw")
160163

161164
docker_args = [
162165
"docker",
163166
"run",
164-
"-v",
165-
self.bind(task.cache_dir, "rw"),
166167
*self.xargs,
167168
]
168169
docker_args.extend(
@@ -195,7 +196,7 @@ class Singularity(Container):
195196
def execute(self, task: "Task[ShellDef]") -> dict[str, ty.Any]:
196197
singularity_img = f"{self.image}:{self.tag}"
197198
# mounting all input locations
198-
mounts, input_updates = self._get_bindings(task=task, root=self.root)
199+
mounts, input_updates = self.get_bindings(task=task, root=self.root)
199200

200201
# todo adding xargsy etc
201202
singularity_args = [

pydra/engine/tests/test_dockertask.py

Lines changed: 72 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import attrs
12
import pytest
23
from pydra.engine.submitter import Submitter
34
from pydra.engine.specs import ShellDef, ShellOutputs
@@ -100,7 +101,10 @@ def test_docker_st_1(run_function, plugin, tmp_path):
100101
docky = Docky().split(executable=cmd)
101102

102103
outputs = run_function(docky, tmp_path, plugin, environment=Docker(image="busybox"))
103-
assert outputs.stdout[0] == f"/mnt/pydra{docky.output_dir[0]}\n"
104+
assert (
105+
outputs.stdout[0]
106+
== f"/mnt/pydra{tmp_path}/{attrs.evolve(docky, executable=cmd[0])._checksum}\n"
107+
)
104108
assert outputs.stdout[1] == "root\n"
105109
assert outputs.return_code[0] == outputs.return_code[1] == 0
106110

@@ -115,8 +119,7 @@ def test_docker_outputspec_1(plugin, tmp_path):
115119
customised output_spec, adding files to the output, providing specific pathname
116120
output_path is automatically added to the bindings
117121
"""
118-
outputs = [shell.out(name="newfile", type=File, help="new file")]
119-
Docky = shell.define("touch newfile_tmp.txt", outputs=outputs)
122+
Docky = shell.define("touch <out|newfile$newfile_tmp.txt>")
120123
docky = Docky()
121124

122125
outputs = docky(plugin=plugin, environment=Docker(image="ubuntu"))
@@ -136,23 +139,22 @@ def test_docker_inputspec_1(tmp_path):
136139

137140
cmd = "cat"
138141

139-
inputs = [
140-
shell.arg(
141-
name="file",
142-
type=File,
143-
position=1,
144-
argstr="",
145-
help="input file",
146-
)
147-
]
148-
149-
docky = shell.define(cmd, inputs=inputs)(
150-
environment=Docker(image="busybox"),
151-
file=filename,
152-
strip=True,
142+
Docky = shell.define(
143+
cmd,
144+
inputs=[
145+
shell.arg(
146+
name="file",
147+
type=File,
148+
position=1,
149+
argstr="",
150+
help="input file",
151+
)
152+
],
153153
)
154154

155-
outputs = docky()
155+
docky = Docky(file=filename)
156+
157+
outputs = docky(environment=Docker(image="busybox"), cache_dir=tmp_path)
156158
assert outputs.stdout.strip() == "hello from pydra"
157159

158160

@@ -168,23 +170,23 @@ def test_docker_inputspec_1a(tmp_path):
168170

169171
cmd = "cat"
170172

171-
inputs = [
172-
shell.arg(
173-
name="file",
174-
type=File,
175-
default=filename,
176-
position=1,
177-
argstr="",
178-
help="input file",
179-
)
180-
]
181-
182-
docky = shell.define(cmd, inputs=inputs)(
183-
environment=Docker(image="busybox"),
184-
strip=True,
173+
Docky = shell.define(
174+
cmd,
175+
inputs=[
176+
shell.arg(
177+
name="file",
178+
type=File,
179+
default=filename,
180+
position=1,
181+
argstr="",
182+
help="input file",
183+
)
184+
],
185185
)
186186

187-
outputs = docky()
187+
docky = Docky()
188+
189+
outputs = docky(environment=Docker(image="busybox"), cache_dir=tmp_path)
188190
assert outputs.stdout.strip() == "hello from pydra"
189191

190192

@@ -476,33 +478,33 @@ def test_docker_wf_inputspec_1(plugin, tmp_path):
476478

477479
cmd = "cat"
478480

479-
inputs = [
480-
shell.arg(
481-
name="file",
482-
type=File,
483-
position=1,
484-
argstr="",
485-
help="input file",
486-
)
487-
]
481+
Docky = shell.define(
482+
cmd,
483+
inputs=[
484+
shell.arg(
485+
name="file",
486+
type=File,
487+
position=1,
488+
argstr="",
489+
help="input file",
490+
)
491+
],
492+
)
488493

489494
@workflow.define
490-
def Workflow(cmd, file):
495+
def Workflow(file):
491496

492497
docky = workflow.add(
493-
shell.define(cmd, inputs=inputs)(
494-
file=file,
495-
environment=Docker(image="busybox"),
496-
strip=True,
497-
)
498+
Docky(file=file),
499+
environment=Docker(image="busybox"),
498500
)
499501

500502
return docky.stdout
501503

502-
wf = Workflow(cmd=cmd, file=filename)
504+
wf = Workflow(file=filename)
503505

504506
outputs = wf()
505-
assert outputs.ou.strip() == "hello from pydra"
507+
assert outputs.out.strip() == "hello from pydra"
506508

507509

508510
@no_win
@@ -532,7 +534,7 @@ def test_docker_wf_state_inputspec_1(plugin, tmp_path):
532534
)
533535

534536
@workflow.define
535-
def Workflow(cmd, file):
537+
def Workflow(file):
536538

537539
docky = workflow.add(
538540
Docky(file=file),
@@ -541,9 +543,9 @@ def Workflow(cmd, file):
541543

542544
return docky.stdout
543545

544-
wf = Workflow(cmd=cmd)
546+
wf = Workflow().split(file=[file_1, file_2])
545547

546-
outputs = wf(split={"file": [file_1, file_2]})
548+
outputs = wf()
547549

548550
assert outputs.out[0].strip() == "hello from pydra"
549551
assert outputs.out[1].strip() == "have a nice one"
@@ -562,30 +564,30 @@ def test_docker_wf_ndst_inputspec_1(plugin, tmp_path):
562564

563565
cmd = "cat"
564566

565-
inputs = [
566-
shell.arg(
567-
name="file",
568-
type=File,
569-
position=1,
570-
argstr="",
571-
help="input file",
572-
)
573-
]
567+
Docky = shell.define(
568+
cmd,
569+
inputs=[
570+
shell.arg(
571+
name="file",
572+
type=File,
573+
position=1,
574+
argstr="",
575+
help="input file",
576+
)
577+
],
578+
)
574579

575580
@workflow.define
576-
def Workflow(cmd, file):
581+
def Workflow(file):
577582

578583
docky = workflow.add(
579-
shell.define(cmd, inputs=inputs)(
580-
environment=Docker(image="busybox"),
581-
file=file,
582-
strip=True,
583-
)
584+
Docky(file=file),
585+
environment=Docker(image="busybox"),
584586
)
585587

586588
return docky.stdout
587589

588-
wf = Workflow(cmd=cmd)
590+
wf = Workflow().split(file=[str(file_1), str(file_2)])
589591

590-
outputs = wf(split={"file": [str(file_1), str(file_2)]})
592+
outputs = wf()
591593
assert outputs.out == ["hello from pydra", "have a nice one"]

0 commit comments

Comments
 (0)