Skip to content

Commit 3dedc5a

Browse files
committed
added test for executable=None (i.e. use entrypoint)
1 parent ee4ba8f commit 3dedc5a

File tree

5 files changed

+70
-12
lines changed

5 files changed

+70
-12
lines changed

pydra/compose/shell/builder.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@
3333
from .task import Task, Outputs
3434

3535

36+
def executable_validator(_, __, value):
37+
"""Validator for the executable attribute of a task"""
38+
if value is None:
39+
return
40+
if not isinstance(value, (str, list)):
41+
raise TypeError(
42+
f"executable must be a string or a list of strings, not {value!r}"
43+
)
44+
if len(value) == 0:
45+
raise ValueError("executable must be a non-empty string or a list of strings")
46+
47+
3648
@dataclass_transform(
3749
kw_only_default=True,
3850
field_specifiers=(field.out, field.outarg),
@@ -207,7 +219,7 @@ def make(
207219
argstr="",
208220
position=0,
209221
default=executable,
210-
validator=attrs.validators.min_len(1),
222+
validator=executable_validator,
211223
help=Task.EXECUTABLE_HELP,
212224
)
213225

pydra/compose/shell/task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,12 @@ def _command_args(self, values: dict[str, ty.Any]) -> list[str]:
294294
if is_fileset_or_union(fld.type) and type(fld_value) is bool:
295295
del values[fld.name]
296296
# Drop special fields that are added separately
297-
del values["executable"]
298297
del values["append_args"]
299298
# Add executable
300299
pos_args = []
301300
if self.executable is not None:
302301
pos_args.append(self._executable_pos_arg(fld, self.executable))
302+
del values["executable"]
303303
positions_provided = [0]
304304
fields = {f.name: f for f in get_fields(self)}
305305
for field_name in values:

pydra/compose/shell/tests/test_shell_fields.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import cloudpickle as cp
77
from pydra.compose import shell
88
from pydra.utils.general import get_fields, task_help, wrap_text
9-
from pydra.compose.shell.builder import _InputPassThrough
9+
from pydra.compose.shell.builder import _InputPassThrough, executable_validator
1010
from fileformats.generic import File, Directory, FsObject
1111
from fileformats import text, image
1212
from pydra.utils.typing import MultiInputObj
@@ -26,7 +26,7 @@ def test_interface_template():
2626
assert sorted_fields(Cp) == [
2727
shell.arg(
2828
name="executable",
29-
validator=attrs.validators.min_len(1),
29+
validator=executable_validator,
3030
default="cp",
3131
type=str | ty.Sequence[str],
3232
position=0,
@@ -81,7 +81,7 @@ def test_interface_template_w_types_and_path_template_ext():
8181
assert sorted_fields(TrimPng) == [
8282
shell.arg(
8383
name="executable",
84-
validator=attrs.validators.min_len(1),
84+
validator=executable_validator,
8585
default="trim-png",
8686
type=str | ty.Sequence[str],
8787
position=0,
@@ -122,7 +122,7 @@ def test_interface_template_w_modify():
122122
assert sorted_fields(TrimPng) == [
123123
shell.arg(
124124
name="executable",
125-
validator=attrs.validators.min_len(1),
125+
validator=executable_validator,
126126
default="trim-png",
127127
type=str | ty.Sequence[str],
128128
position=0,
@@ -181,7 +181,7 @@ def test_interface_template_more_complex():
181181
assert sorted_fields(Cp) == [
182182
shell.arg(
183183
name="executable",
184-
validator=attrs.validators.min_len(1),
184+
validator=executable_validator,
185185
default="cp",
186186
type=str | ty.Sequence[str],
187187
position=0,
@@ -281,7 +281,7 @@ def test_interface_template_with_overrides_and_optionals():
281281
assert sorted_fields(Cp) == [
282282
shell.arg(
283283
name="executable",
284-
validator=attrs.validators.min_len(1),
284+
validator=executable_validator,
285285
default="cp",
286286
type=str | ty.Sequence[str],
287287
position=0,
@@ -353,7 +353,7 @@ def test_interface_template_with_defaults():
353353
assert sorted_fields(Cp) == [
354354
shell.arg(
355355
name="executable",
356-
validator=attrs.validators.min_len(1),
356+
validator=executable_validator,
357357
default="cp",
358358
type=str | ty.Sequence[str],
359359
position=0,
@@ -421,7 +421,7 @@ def test_interface_template_with_type_overrides():
421421
assert sorted_fields(Cp) == [
422422
shell.arg(
423423
name="executable",
424-
validator=attrs.validators.min_len(1),
424+
validator=executable_validator,
425425
default="cp",
426426
type=str | ty.Sequence[str],
427427
position=0,
@@ -738,7 +738,7 @@ class Outputs(shell.Outputs):
738738
assert sorted_fields(A) == [
739739
shell.arg(
740740
name="executable",
741-
validator=attrs.validators.min_len(1),
741+
validator=executable_validator,
742742
default="cp",
743743
type=str | ty.Sequence[str],
744744
argstr="",

pydra/environments/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def read_and_display(
236236
stdout = stdout.strip()
237237
stderr = process.stderr.decode("utf-8")
238238
if process.returncode:
239-
msg = f"Error executing command {cmd} (code: {process.returncode}):"
239+
msg = f"Error executing command {' '.join(cmd)!r} (code: {process.returncode}):"
240240
if stdout:
241241
msg += "\n\nstderr:\n" + stderr
242242
if stdout:

pydra/environments/tests/test_environments.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22
import typing as ty
3+
import docker as docker_engine
34
from pydra.environments import native, docker, singularity
45
from pydra.engine.submitter import Submitter
56
from fileformats.generic import File
@@ -503,3 +504,48 @@ def newcache(x):
503504
"file_1_copy.txt",
504505
"file_2_copy.txt",
505506
]
507+
508+
509+
@no_win
510+
@need_docker
511+
def test_entrypoint(tmp_path):
512+
"""docker env: task with a file in the output"""
513+
514+
dc = docker_engine.from_env()
515+
516+
# Create executable that runs validator then produces some mock output
517+
# files
518+
build_dir = tmp_path / "build"
519+
build_dir.mkdir()
520+
entrypoint = build_dir / "entrypoint.sh"
521+
with open(entrypoint, "w") as f:
522+
f.write("#!/bin/sh\necho hello $1")
523+
524+
IMAGE_TAG = "pydra-test-entrypoint"
525+
526+
# Build mock BIDS app image
527+
with open(build_dir / "Dockerfile", "w") as f:
528+
f.write(
529+
"""FROM busybox
530+
ADD ./entrypoint.sh /entrypoint.sh
531+
RUN chmod +x /entrypoint.sh
532+
ENTRYPOINT ["/entrypoint.sh"]"""
533+
)
534+
535+
dc.images.build(path=str(build_dir), tag=IMAGE_TAG + ":latest")
536+
537+
@shell.define
538+
class TestEntrypoint(shell.Task):
539+
"""task with a file in the output"""
540+
541+
executable = None
542+
persons_name: str = shell.arg(help="the name of the person to say hello to")
543+
544+
class Outputs(shell.Outputs):
545+
pass
546+
547+
test_entrypoint = TestEntrypoint(persons_name="Guido")
548+
549+
outputs = test_entrypoint(environment=docker.Environment(image=IMAGE_TAG))
550+
551+
assert outputs.stdout == "hello Guido\n"

0 commit comments

Comments
 (0)