Skip to content

Commit 2e01e81

Browse files
committed
WIP: --single-process inherit reqs & hints
1 parent 8720230 commit 2e01e81

File tree

7 files changed

+122
-29
lines changed

7 files changed

+122
-29
lines changed

cwltool/argparser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,8 +656,10 @@ def arg_parser() -> argparse.ArgumentParser:
656656
default=None,
657657
help="Only executes the underlying Process (CommandLineTool, "
658658
"ExpressionTool, or sub-Workflow) for the given step in a workflow. "
659-
"This will not include any step-level processing: scatter, when, no "
660-
"processing of step-level default, or valueFrom input modifiers. "
659+
"This will not include any step-level processing: 'scatter', 'when'; "
660+
"and there will be no processing of step-level 'default', or 'valueFrom' "
661+
"input modifiers. However, requirements/hints from the step or parent "
662+
"workflow(s) will be inherited as usual."
661663
"The input object must match that Process's inputs.",
662664
)
663665

cwltool/main.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,32 @@ def my_represent_none(
754754
)
755755

756756

757+
def inherit_reqshints(tool: Process, parent: Process) -> None:
758+
"""Copy down requirements and hints from ancestors of a given process."""
759+
for parent_req in parent.requirements:
760+
found = False
761+
for tool_req in tool.requirements:
762+
if parent_req["class"] == tool_req["class"]:
763+
found = True
764+
break
765+
if not found:
766+
tool.requirements.append(parent_req)
767+
for parent_hint in parent.hints:
768+
found = False
769+
for tool_req in tool.requirements:
770+
if parent_hint["class"] == tool_req["class"]:
771+
found = True
772+
break
773+
if not found:
774+
for tool_hint in tool.hints:
775+
if parent_hint["class"] == tool_hint["class"]:
776+
found = True
777+
break
778+
if not found:
779+
tool.hints.append(parent_req)
780+
# TODO: get the parent of the parent and recurse
781+
782+
757783
def choose_target(
758784
args: argparse.Namespace,
759785
tool: Process,
@@ -826,36 +852,33 @@ def choose_process(
826852
tool: Process,
827853
loadingContext: LoadingContext,
828854
) -> Optional[Process]:
829-
"""Walk the given Workflow and extract just args.single_step."""
855+
"""Walk the given Workflow and extract just args.single_process."""
830856
if loadingContext.loader is None:
831857
raise Exception("loadingContext.loader cannot be None")
832858

833859
if isinstance(tool, Workflow):
834860
url = urllib.parse.urlparse(tool.tool["id"])
835861
if url.fragment:
836-
extracted = get_process(
837-
tool,
838-
tool.tool["id"] + "/" + args.single_process,
839-
loadingContext.loader.idx,
840-
)
862+
step_id = tool.tool["id"] + "/" + args.single_process
841863
else:
842-
extracted = get_process(
843-
tool,
844-
loadingContext.loader.fetcher.urljoin(
845-
tool.tool["id"], "#" + args.single_process
846-
),
847-
loadingContext.loader.idx,
864+
step_id = loadingContext.loader.fetcher.urljoin(
865+
tool.tool["id"], "#" + args.single_process
848866
)
867+
extracted, step_pos = get_process(
868+
tool,
869+
step_id,
870+
loadingContext.loader.idx,
871+
)
849872
else:
850873
_logger.error("Can only use --single-process on Workflows")
851874
return None
852875
if isinstance(loadingContext.loader.idx, MutableMapping):
853876
loadingContext.loader.idx[extracted["id"]] = extracted
854-
tool = make_tool(extracted["id"], loadingContext)
877+
new_tool = make_tool(extracted["id"], loadingContext)
855878
else:
856879
raise Exception("Missing loadingContext.loader.idx!")
857-
858-
return tool
880+
inherit_reqshints(new_tool, tool.steps[step_pos])
881+
return new_tool
859882

860883

861884
def check_working_directories(

cwltool/subgraph.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from ruamel.yaml.comments import CommentedMap
1717

18+
from .process import Process
1819
from .utils import CWLObjectType, aslist
1920
from .workflow import Workflow, WorkflowStep
2021

@@ -55,11 +56,14 @@ def declare_node(nodes: Dict[str, Node], nodeid: str, tp: Optional[str]) -> Node
5556
return nodes[nodeid]
5657

5758

58-
def find_step(steps: List[WorkflowStep], stepid: str) -> Optional[CWLObjectType]:
59-
for st in steps:
59+
def find_step(
60+
steps: List[WorkflowStep], stepid: str
61+
) -> Tuple[Optional[CWLObjectType], int]:
62+
"""Find the step and step_index for a given step id."""
63+
for index, st in enumerate(steps):
6064
if st.tool["id"] == stepid:
61-
return st.tool
62-
return None
65+
return st.tool, index
66+
return None, -1
6367

6468

6569
def get_subgraph(roots: MutableSequence[str], tool: Workflow) -> CommentedMap:
@@ -124,7 +128,7 @@ def get_subgraph(roots: MutableSequence[str], tool: Workflow) -> CommentedMap:
124128
df = urllib.parse.urldefrag(u)
125129
rn = str(df[0] + "#" + df[1].replace("/", "_"))
126130
if nodes[v].type == STEP:
127-
wfstep = find_step(tool.steps, v)
131+
wfstep = find_step(tool.steps, v)[0]
128132
if wfstep is not None:
129133
for inp in cast(
130134
MutableSequence[CWLObjectType], wfstep["inputs"]
@@ -169,7 +173,7 @@ def get_step(tool: Workflow, step_id: str) -> CommentedMap:
169173

170174
extracted = CommentedMap()
171175

172-
step = find_step(tool.steps, step_id)
176+
step = find_step(tool.steps, step_id)[0]
173177
if step is None:
174178
raise Exception(f"Step {step_id} was not found")
175179

@@ -197,15 +201,18 @@ def get_step(tool: Workflow, step_id: str) -> CommentedMap:
197201
return extracted
198202

199203

200-
def get_process(tool: Workflow, step_id: str, index: Mapping[str, Any]) -> Any:
201-
"""Return just a single Process from a Workflow step."""
202-
step = find_step(tool.steps, step_id)
204+
def get_process(
205+
tool: Workflow, step_id: str, index: Mapping[str, Any]
206+
) -> Tuple[Any, int]:
207+
"""Find the underlying Process for a given Workflow step id."""
208+
step, step_pos = find_step(tool.steps, step_id)
203209
if step is None:
204210
raise Exception(f"Step {step_id} was not found")
205211

206212
run = step["run"]
207213

208214
if isinstance(run, str):
209-
return index[run]
215+
process = index[run]
210216
else:
211-
return run
217+
process = run
218+
return process, step_pos

tests/subgraph/env-job.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"in": "hello test env"
3+
}

tests/subgraph/env-tool2.cwl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class: CommandLineTool
2+
cwlVersion: v1.2
3+
inputs:
4+
in: string
5+
outputs:
6+
out:
7+
type: File
8+
outputBinding:
9+
glob: out
10+
11+
hints:
12+
EnvVarRequirement:
13+
envDef:
14+
TEST_ENV: $(inputs.in)
15+
16+
baseCommand: ["/bin/sh", "-c", "echo $TEST_ENV"]
17+
18+
stdout: out

tests/subgraph/env-wf2.cwl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.2
4+
5+
inputs:
6+
in: string
7+
8+
outputs:
9+
out:
10+
type: File
11+
outputSource: step1/out
12+
13+
requirements:
14+
EnvVarRequirement:
15+
envDef:
16+
TEST_ENV: override
17+
18+
steps:
19+
step1:
20+
run: env-tool2.cwl
21+
in:
22+
in: in
23+
out: [out]

tests/test_subgraph.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from cwltool.subgraph import get_step, get_subgraph
1010
from cwltool.workflow import Workflow, default_make_tool
1111

12-
from .util import get_data
12+
from .util import get_data, get_main_output
1313

1414

1515
def clean(val: Any, path: str) -> Any:
@@ -87,3 +87,20 @@ def test_get_step() -> None:
8787
extracted = get_step(tool, wf + "#" + a)
8888
with open(get_data("tests/subgraph/single_" + a + ".json")) as f:
8989
assert json.load(f) == clean(convert_to_dict(extracted), sg)
90+
91+
92+
def test_single_process_inherit_reqshints() -> None:
93+
"""Inherit reqs and hints from parent(s) with --single-step."""
94+
err_code, stdout, stderr = get_main_output(
95+
[
96+
"--single-process",
97+
"step1",
98+
get_data("tests/subgraph/env-wf2.cwl"),
99+
get_data("tests/subgraph/env-job.json"),
100+
]
101+
)
102+
assert err_code == 0
103+
assert (
104+
json.loads(stdout)["out"]["checksum"]
105+
== "sha1$cdc1e84968261d6a7575b5305945471f8be199b6"
106+
)

0 commit comments

Comments
 (0)