Skip to content

Commit 9453f0b

Browse files
manabuishiimr-c
andcommitted
docker-extract & cite-extract: support CWL v1.1 and v1.2
Co-authored-by: Michael R. Crusoe <[email protected]>
1 parent 406aae8 commit 9453f0b

17 files changed

+403
-161
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ docs: FORCE
7979

8080
## clean : clean up all temporary / machine-generated files
8181
clean: FORCE
82-
rm -f ${MODILE}/*.pyc tests/*.pyc
82+
rm -f ${MODULE}/*.pyc tests/*.pyc
8383
python setup.py clean --all || true
8484
rm -Rf .coverage
8585
rm -f diff-cover.html

create_cwl_from_objects.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env python3
22
# SPDX-License-Identifier: Apache-2.0
3-
from ruamel import yaml
3+
import sys
4+
5+
import ruamel.yaml
46

57
from cwl_utils.parser import cwl_v1_2 as cwl
68

@@ -23,7 +25,8 @@ def main() -> None:
2325
stdin="$(inputs.file1.path)",
2426
stdout="output",
2527
)
26-
print(yaml.main.round_trip_dump(cat_tool.save()))
28+
yaml = ruamel.yaml.YAML()
29+
yaml.dump(cat_tool.save(), sys.stdout)
2730

2831

2932
if __name__ == "__main__":

cwl_utils/cite_extract.py

+21-43
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
# SPDX-License-Identifier: Apache-2.0
33
import argparse
44
import sys
5-
from typing import Iterator, List, Union, cast
5+
from typing import Iterator, cast
66

7-
import cwl_utils.parser.cwl_v1_0 as cwl
8-
9-
ProcessType = Union[cwl.Workflow, cwl.CommandLineTool, cwl.ExpressionTool]
7+
import cwl_utils.parser as cwl
108

119

1210
def arg_parser() -> argparse.ArgumentParser:
@@ -21,50 +19,30 @@ def arg_parser() -> argparse.ArgumentParser:
2119
return parser
2220

2321

24-
def parse_args(args: List[str]) -> argparse.Namespace:
25-
"""Parse the command line arguments."""
26-
return arg_parser().parse_args(args)
27-
28-
2922
def run(args: argparse.Namespace) -> int:
3023
"""Extract the software requirements."""
31-
top = cwl.load_document(args.input)
32-
traverse(top)
24+
for req in traverse(cwl.load_document_by_uri(args.input)):
25+
process_software_requirement(req)
3326
return 0
3427

3528

36-
def main() -> None:
29+
def main() -> int:
3730
"""Console entry point."""
38-
sys.exit(run(parse_args(sys.argv[1:])))
39-
40-
41-
def extract_software_packages(process: ProcessType) -> None:
42-
"""Print software packages found in the given process."""
43-
for req in extract_software_reqs(process):
44-
print(process.id)
45-
process_software_requirement(req)
31+
return run(arg_parser().parse_args(sys.argv[1:]))
4632

4733

4834
def extract_software_reqs(
49-
process: ProcessType,
35+
process: cwl.Process,
5036
) -> Iterator[cwl.SoftwareRequirement]:
5137
"""Return an iterator over any SoftwareRequirements found in the given process."""
5238
if process.requirements:
5339
for req in process.requirements:
54-
if isinstance(req, cwl.SoftwareRequirement):
40+
if isinstance(req, cwl.SoftwareRequirementTypes):
5541
yield req
5642
if process.hints:
5743
for req in process.hints:
58-
if isinstance(req, cwl.ProcessRequirement):
59-
if isinstance(req, cwl.SoftwareRequirement):
60-
yield req
61-
elif req["class"] == "SoftwareRequirement":
62-
yield cwl.load_field(
63-
req,
64-
cwl.SoftwareRequirementLoader,
65-
process.id if process.id else "",
66-
process.loadingOptions,
67-
)
44+
if isinstance(req, cwl.SoftwareRequirementTypes):
45+
yield req
6846

6947

7048
def process_software_requirement(req: cwl.SoftwareRequirement) -> None:
@@ -77,26 +55,26 @@ def process_software_requirement(req: cwl.SoftwareRequirement) -> None:
7755
)
7856

7957

80-
def traverse(process: ProcessType) -> None:
58+
def traverse(process: cwl.Process) -> Iterator[cwl.SoftwareRequirement]:
8159
"""Extract the software packages for this process, and any steps."""
82-
extract_software_packages(process)
83-
if isinstance(process, cwl.Workflow):
84-
traverse_workflow(process)
60+
yield from extract_software_reqs(process)
61+
if isinstance(process, cwl.WorkflowTypes):
62+
yield from traverse_workflow(process)
8563

8664

87-
def get_process_from_step(step: cwl.WorkflowStep) -> ProcessType:
65+
def get_process_from_step(step: cwl.WorkflowStep) -> cwl.Process:
8866
"""Return the process for this step, loading it if needed."""
8967
if isinstance(step.run, str):
90-
return cast(ProcessType, cwl.load_document(step.run))
91-
return cast(ProcessType, step.run)
68+
return cast(cwl.Process, cwl.load_document_by_uri(step.run))
69+
return cast(cwl.Process, step.run)
9270

9371

94-
def traverse_workflow(workflow: cwl.Workflow) -> None:
72+
def traverse_workflow(workflow: cwl.Workflow) -> Iterator[cwl.SoftwareRequirement]:
9573
"""Iterate over the given workflow, extracting the software packages."""
9674
for step in workflow.steps:
97-
extract_software_packages(step)
98-
traverse(get_process_from_step(step))
75+
yield from extract_software_reqs(step)
76+
yield from traverse(get_process_from_step(step))
9977

10078

10179
if __name__ == "__main__":
102-
main()
80+
sys.exit(main())

cwl_utils/docker_extract.py

+44-42
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@
33
import argparse
44
import os
55
import sys
6-
from pathlib import Path
7-
from typing import Iterator, List, Union, cast
6+
from typing import Iterator, List, cast
87

9-
import cwl_utils.parser.cwl_v1_0 as cwl
8+
import ruamel.yaml
9+
10+
import cwl_utils.parser as cwl
1011
from cwl_utils.image_puller import (
1112
DockerImagePuller,
1213
ImagePuller,
1314
SingularityImagePuller,
1415
)
1516

16-
ProcessType = Union[cwl.Workflow, cwl.CommandLineTool, cwl.ExpressionTool]
17-
1817

1918
def arg_parser() -> argparse.ArgumentParser:
2019
"""Argument parser."""
@@ -35,41 +34,51 @@ def arg_parser() -> argparse.ArgumentParser:
3534
parser.add_argument(
3635
"--container-engine",
3736
dest="container_engine",
38-
default="docker",
39-
help="Specify which command to use to run OCI containers.",
37+
help="Specify which command to use to run OCI containers. "
38+
"Defaults to 'docker' (or 'singularity' if --singularity/-s is passed).",
4039
)
4140
return parser
4241

4342

44-
def parse_args(args: List[str]) -> argparse.Namespace:
45-
"""Parse the command line arguments."""
46-
return arg_parser().parse_args(args)
47-
48-
49-
def run(args: argparse.Namespace) -> int:
50-
"""Extract the docker reqs and download them using Singularity or docker."""
43+
def run(args: argparse.Namespace) -> List[cwl.DockerRequirement]:
44+
"""Extract the docker reqs and download them using Singularity or Docker."""
5145
os.makedirs(args.dir, exist_ok=True)
5246

53-
top = cwl.load_document(args.input)
47+
top = cwl.load_document_by_uri(args.input)
48+
reqs: List[cwl.DockerRequirement] = []
5449

5550
for req in traverse(top):
51+
reqs.append(req)
5652
if not req.dockerPull:
57-
print(f"Unable to save image from {req} due to lack of 'dockerPull'.")
53+
print(
54+
"Unable to save image from due to lack of 'dockerPull':",
55+
file=sys.stderr,
56+
)
57+
yaml = ruamel.yaml.YAML()
58+
yaml.dump(req.save(), sys.stderr)
5859
continue
5960
if args.singularity:
6061
image_puller: ImagePuller = SingularityImagePuller(
61-
req.dockerPull, args.dir, "singularity"
62+
req.dockerPull,
63+
args.dir,
64+
args.container_engine
65+
if args.container_engine is not None
66+
else "singularity",
6267
)
6368
else:
6469
image_puller = DockerImagePuller(
65-
req.dockerPull, args.dir, args.container_engine
70+
req.dockerPull,
71+
args.dir,
72+
args.container_engine
73+
if args.container_engine is not None
74+
else "docker",
6675
)
6776
image_puller.save_docker_image()
68-
return 0
77+
return reqs
6978

7079

7180
def extract_docker_requirements(
72-
process: ProcessType,
81+
process: cwl.Process,
7382
) -> Iterator[cwl.DockerRequirement]:
7483
"""Yield an iterator of the docker reqs, normalizing the pull request."""
7584
for req in extract_docker_reqs(process):
@@ -78,38 +87,30 @@ def extract_docker_requirements(
7887
yield req
7988

8089

81-
def extract_docker_reqs(process: ProcessType) -> Iterator[cwl.DockerRequirement]:
90+
def extract_docker_reqs(process: cwl.Process) -> Iterator[cwl.DockerRequirement]:
8291
"""For the given process, extract the DockerRequirement(s)."""
8392
if process.requirements:
8493
for req in process.requirements:
85-
if isinstance(req, cwl.DockerRequirement):
94+
if isinstance(req, cwl.DockerRequirementTypes):
8695
yield req
8796
if process.hints:
8897
for req in process.hints:
89-
if isinstance(req, cwl.ProcessRequirement):
90-
if isinstance(req, cwl.DockerRequirement):
91-
yield req
92-
elif req["class"] == "DockerRequirement":
93-
yield cwl.load_field(
94-
req,
95-
cwl.DockerRequirementLoader,
96-
Path.cwd().as_uri(),
97-
process.loadingOptions,
98-
)
99-
100-
101-
def traverse(process: ProcessType) -> Iterator[cwl.DockerRequirement]:
98+
if isinstance(req, cwl.DockerRequirementTypes):
99+
yield req
100+
101+
102+
def traverse(process: cwl.Process) -> Iterator[cwl.DockerRequirement]:
102103
"""Yield the iterator for the docker reqs, including an workflow steps."""
103104
yield from extract_docker_requirements(process)
104-
if isinstance(process, cwl.Workflow):
105+
if isinstance(process, cwl.WorkflowTypes):
105106
yield from traverse_workflow(process)
106107

107108

108-
def get_process_from_step(step: cwl.WorkflowStep) -> ProcessType:
109+
def get_process_from_step(step: cwl.WorkflowStep) -> cwl.Process:
109110
"""Return the process for this step, loading it if necessary."""
110111
if isinstance(step.run, str):
111-
return cast(ProcessType, cwl.load_document(step.run))
112-
return cast(ProcessType, step.run)
112+
return cast(cwl.Process, cwl.load_document_by_uri(step.run))
113+
return cast(cwl.Process, step.run)
113114

114115

115116
def traverse_workflow(workflow: cwl.Workflow) -> Iterator[cwl.DockerRequirement]:
@@ -119,10 +120,11 @@ def traverse_workflow(workflow: cwl.Workflow) -> Iterator[cwl.DockerRequirement]
119120
yield from traverse(get_process_from_step(step))
120121

121122

122-
def main() -> None:
123+
def main() -> int:
123124
"""Command line entry point."""
124-
sys.exit(run(parse_args(sys.argv[1:])))
125+
run(arg_parser().parse_args(sys.argv[1:]))
126+
return 0
125127

126128

127129
if __name__ == "__main__":
128-
main()
130+
sys.exit(main())

cwl_utils/image_puller.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import os
55
import subprocess # nosec
66
from abc import ABC, abstractmethod
7-
from typing import List
7+
from pathlib import Path
8+
from typing import List, Union
89

910
from .singularity import get_version as get_singularity_version
1011
from .singularity import is_version_2_6 as is_singularity_version_2_6
@@ -15,7 +16,7 @@
1516

1617

1718
class ImagePuller(ABC):
18-
def __init__(self, req: str, save_directory: str, cmd: str) -> None:
19+
def __init__(self, req: str, save_directory: Union[str, Path], cmd: str) -> None:
1920
"""Create an ImagePuller."""
2021
self.req = req
2122
self.save_directory = save_directory
@@ -81,10 +82,6 @@ class SingularityImagePuller(ImagePuller):
8182
CHARS_TO_REPLACE = ["/"]
8283
NEW_CHAR = "_"
8384

84-
def __init__(self, req: str, save_directory: str, cmd: str) -> None:
85-
"""Create a Singularity-based software container image downloader."""
86-
super().__init__(req, save_directory, cmd)
87-
8885
def get_image_name(self) -> str:
8986
"""Determine the file name appropriate to the installed version of Singularity."""
9087
image_name = self.req

cwl_utils/parser/__init__.py

+23
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,28 @@
8585
"""Type union for a CWL v1.x DockerRequirement object."""
8686
Process = Union[Workflow, CommandLineTool, ExpressionTool, cwl_v1_2.Operation]
8787
"""Type Union for a CWL v1.x Process object."""
88+
ProcessRequirement = Union[
89+
cwl_v1_0.ProcessRequirement,
90+
cwl_v1_1.ProcessRequirement,
91+
cwl_v1_2.ProcessRequirement,
92+
]
93+
"""Type Union for a CWL v1.x ProcessRequirement object."""
94+
ProcessRequirementTypes = (
95+
cwl_v1_0.ProcessRequirement,
96+
cwl_v1_1.ProcessRequirement,
97+
cwl_v1_2.ProcessRequirement,
98+
)
99+
SoftwareRequirement = Union[
100+
cwl_v1_0.SoftwareRequirement,
101+
cwl_v1_1.SoftwareRequirement,
102+
cwl_v1_2.SoftwareRequirement,
103+
]
104+
SoftwareRequirementTypes = (
105+
cwl_v1_0.SoftwareRequirement,
106+
cwl_v1_1.SoftwareRequirement,
107+
cwl_v1_2.SoftwareRequirement,
108+
)
109+
"""Type union for a CWL v1.x SoftwareRequirement object."""
88110
ArraySchema = Union[cwl_v1_0.ArraySchema, cwl_v1_1.ArraySchema, cwl_v1_2.ArraySchema]
89111
"""Type Union for a CWL v1.x ArraySchema object."""
90112
EnumSchema = Union[cwl_v1_0.EnumSchema, cwl_v1_1.EnumSchema, cwl_v1_2.EnumSchema]
@@ -103,6 +125,7 @@
103125
"""Type Union for a CWL v1.x Dirent object."""
104126

105127
_Loader = Union[cwl_v1_0._Loader, cwl_v1_1._Loader, cwl_v1_2._Loader]
128+
"""Type union for a CWL v1.x _Loader."""
106129

107130

108131
def _get_id_from_graph(yaml: MutableMapping[str, Any], id_: Optional[str]) -> Any:

cwl_utils/parser/cwl_v1_0_utils.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from collections import namedtuple
55
from typing import IO, Any, Dict, List, MutableSequence, Optional, Tuple, Union, cast
66

7-
from ruamel import yaml
87
from schema_salad.exceptions import ValidationException
98
from schema_salad.sourceline import SourceLine
109
from schema_salad.utils import aslist, json_dumps
@@ -13,6 +12,7 @@
1312
import cwl_utils.parser.cwl_v1_0 as cwl
1413
import cwl_utils.parser.utils
1514
from cwl_utils.errors import WorkflowException
15+
from cwl_utils.utils import yaml_dumps
1616

1717
CONTENT_LIMIT: int = 64 * 1024
1818

@@ -297,7 +297,7 @@ def type_for_step_output(
297297
raise ValidationException(
298298
"param {} not found in {}.".format(
299299
sourcename,
300-
yaml.main.round_trip_dump(cwl.save(step_run)),
300+
yaml_dumps(cwl.save(step_run)),
301301
)
302302
)
303303

@@ -428,9 +428,7 @@ def param_for_source_id(
428428
raise WorkflowException(
429429
"param {} not found in {}\n{}.".format(
430430
sourcename,
431-
yaml.main.round_trip_dump(cwl.save(process)),
432-
f" or\n {yaml.main.round_trip_dump(cwl.save(parent))}"
433-
if parent is not None
434-
else "",
431+
yaml_dumps(cwl.save(process)),
432+
f" or\n {yaml_dumps(cwl.save(parent))}" if parent is not None else "",
435433
)
436434
)

0 commit comments

Comments
 (0)