diff --git a/build-cwltool-docker.sh b/build-cwltool-docker.sh index 9f7163afb..3dfa7bb66 100755 --- a/build-cwltool-docker.sh +++ b/build-cwltool-docker.sh @@ -8,4 +8,4 @@ ${engine} run -t -v /var/run/docker.sock:/var/run/docker.sock \ -v /tmp:/tmp \ -v "$PWD":/tmp/cwltool \ quay.io/commonwl/cwltool_module /bin/sh -c \ - "apk add gcc bash git && pip install -r/tmp/cwltool/test-requirements.txt ; pytest -k 'not (test_bioconda or test_double_overwrite or test_env_filtering or test_biocontainers or test_disable_file_overwrite_without_ext or test_disable_file_creation_in_outdir_with_ext or test_write_write_conflict or test_directory_literal_with_real_inputs_inside or test_revsort_workflow or test_stdin_with_id_preset or test_no_compute_chcksum or test_packed_workflow_execution[tests/wf/count-lines1-wf.cwl-tests/wf/wc-job.json-False] or test_sequential_workflow or test_single_process_subwf_subwf_inline_step or test_cache_dockerreq_hint_instead_of_req)' --ignore-glob '*test_udocker.py' -n 0 -v -rs --pyargs cwltool" + "apk add gcc bash git && pip install -r/tmp/cwltool/test-requirements.txt ; pytest -k 'not (test_bioconda or test_double_overwrite or test_env_filtering or test_biocontainers or test_disable_file_overwrite_without_ext or test_disable_file_creation_in_outdir_with_ext or test_write_write_conflict or test_directory_literal_with_real_inputs_inside or test_revsort_workflow or test_stdin_with_id_preset or test_no_compute_chcksum or test_packed_workflow_execution[tests/wf/count-lines1-wf.cwl-tests/wf/wc-job.json-False] or test_sequential_workflow or test_single_process_subwf_subwf_inline_step or test_cache_dockerreq_hint_instead_of_req or test_workflow_job_step_name_callback)' --ignore-glob '*test_udocker.py' -n 0 -v -rs --pyargs cwltool" diff --git a/cwltool/context.py b/cwltool/context.py index bb281fd88..83dd5a190 100644 --- a/cwltool/context.py +++ b/cwltool/context.py @@ -29,6 +29,7 @@ from .process import Process from .secrets import SecretStore from .software_requirements import DependenciesConfiguration + from .workflow_job import WorkflowJobStep class ContextBase: @@ -191,6 +192,10 @@ def __init__(self, kwargs: Optional[dict[str, Any]] = None) -> None: self.default_stderr: Optional[Union[IO[bytes], TextIO]] = None self.validate_only: bool = False self.validate_stdout: Optional["SupportsWrite[str]"] = None + self.workflow_job_step_name_callback: Optional[ + Callable[[WorkflowJobStep, CWLObjectType], str] + ] = None + super().__init__(kwargs) if self.tmp_outdir_prefix == "": self.tmp_outdir_prefix = self.tmpdir_prefix diff --git a/cwltool/workflow_job.py b/cwltool/workflow_job.py index b552641e1..368d1cc02 100644 --- a/cwltool/workflow_job.py +++ b/cwltool/workflow_job.py @@ -60,7 +60,12 @@ def job( ) -> JobsGeneratorType: runtimeContext = runtimeContext.copy() runtimeContext.part_of = self.name - runtimeContext.name = shortname(self.id) + + if runtimeContext.workflow_job_step_name_callback is not None: + vfinputs = {shortname(k): v for k, v in joborder.items()} + runtimeContext.name = runtimeContext.workflow_job_step_name_callback(self, vfinputs) + else: + runtimeContext.name = shortname(self.id) _logger.info("[%s] start", self.name) diff --git a/tests/test_context.py b/tests/test_context.py index acb7dfbca..be1d89575 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,10 +1,16 @@ import subprocess import sys +import logging +from io import StringIO +from typing import MutableMapping, cast from cwltool.context import RuntimeContext from cwltool.factory import Factory +from cwltool.utils import CWLObjectType +from cwltool.workflow_job import WorkflowJobStep +from pathlib import Path -from .util import get_data +from .util import get_data, needs_docker def test_replace_default_stdout_stderr() -> None: @@ -26,3 +32,59 @@ def test_replace_default_stdout_stderr() -> None: assert echo(inp="foo") == {"out": "foo\n"} sys.stdout = original_stdout sys.stderr = original_stderr + + +@needs_docker +def test_workflow_job_step_name_callback() -> None: + """Test ability to hook custom workflow step naming""" + + stream = StringIO() + streamhandler = logging.StreamHandler(stream) + _logger = logging.getLogger("cwltool") + _logger.addHandler(streamhandler) + + try: + runtime_context = RuntimeContext() + + def step_name_hook(step: WorkflowJobStep, job: CWLObjectType) -> str: + j1 = cast(MutableMapping[str, CWLObjectType], job) + inp = cast(MutableMapping[str, str], j1.get("revtool_input", j1.get("sorted_input"))) + return "%s on %s" % ( + step.name, + inp.get("basename"), + ) + + runtime_context.workflow_job_step_name_callback = step_name_hook + + factory = Factory(None, None, runtime_context) + revsort = factory.make(get_data("tests/wf/revsort.cwl")) + + result = revsort( + workflow_input={ + "class": "File", + "location": Path(get_data("tests/wf/whale.txt")).as_uri(), + "format": "https://www.iana.org/assignments/media-types/text/plain", + } + ) + + result = cast(CWLObjectType, result) + + sorted_out = cast(MutableMapping[str, str], result["sorted_output"]) + loc = sorted_out["location"] + + assert result == { + "sorted_output": { + "basename": "output.txt", + "checksum": "sha1$b9214658cc453331b62c2282b772a5c063dbd284", + "class": "File", + "http://commonwl.org/cwltool#generation": 0, + "nameext": ".txt", + "nameroot": "output", + "size": 1111, + "location": loc, + }, + } + + assert "rev on whale.txt" in stream.getvalue() + finally: + _logger.removeHandler(streamhandler)