Skip to content

Commit 99f3b25

Browse files
authored
Initial "all-in-one" normalizer script (#63)
1 parent 63f779f commit 99f3b25

File tree

4 files changed

+164
-1
lines changed

4 files changed

+164
-1
lines changed

cwl_utils/cwl_normalizer.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Copyright 2021 Michael R. Crusoe
4+
"""Normalize CWL documents to CWL v1.2, JSON style."""
5+
import argparse
6+
import logging
7+
import sys
8+
import tempfile
9+
from pathlib import Path
10+
from typing import (
11+
List,
12+
MutableSequence,
13+
Optional,
14+
Set,
15+
)
16+
17+
from cwltool.context import LoadingContext, RuntimeContext
18+
from cwltool.load_tool import (
19+
fetch_document,
20+
resolve_and_validate_document,
21+
resolve_tool_uri,
22+
)
23+
from cwltool.loghandler import _logger as _cwltoollogger
24+
from cwltool.main import print_pack
25+
from cwltool.process import use_standard_schema
26+
from cwlupgrader import main as cwlupgrader
27+
from ruamel import yaml
28+
from schema_salad.sourceline import add_lc_filename
29+
30+
_logger = logging.getLogger("cwl-normalizer") # pylint: disable=invalid-name
31+
defaultStreamHandler = logging.StreamHandler() # pylint: disable=invalid-name
32+
_logger.addHandler(defaultStreamHandler)
33+
_logger.setLevel(logging.INFO)
34+
_cwltoollogger.setLevel(100)
35+
36+
from cwl_utils import cwl_v1_2_expression_refactor
37+
from cwl_utils.parser_v1_2 import load_document_by_yaml, save
38+
39+
40+
def parse_args(args: List[str]) -> argparse.Namespace:
41+
"""Argument parser."""
42+
parser = argparse.ArgumentParser(
43+
description="Tool to normalize CWL documents. Will upgrade to CWL v1.2, "
44+
"and pack the result. Can optionally refactor out CWL expressions."
45+
)
46+
parser.add_argument(
47+
"--etools",
48+
help="Output ExpressionTools, don't go all the way to CommandLineTools.",
49+
action="store_true",
50+
)
51+
parser.add_argument(
52+
"--skip-some1",
53+
help="Don't process CommandLineTool.inputs.inputBinding and CommandLineTool.arguments sections.",
54+
action="store_true",
55+
)
56+
parser.add_argument(
57+
"--skip-some2",
58+
help="Don't process CommandLineTool.outputEval or CommandLineTool.requirements.InitialWorkDirRequirement.",
59+
action="store_true",
60+
)
61+
parser.add_argument(
62+
"--no-expression-refactoring",
63+
help="Don't do any CWL expression refactoring.",
64+
action="store_true",
65+
)
66+
parser.add_argument("dir", help="Directory in which to save converted files")
67+
parser.add_argument(
68+
"inputs",
69+
nargs="+",
70+
help="One or more CWL documents.",
71+
)
72+
return parser.parse_args(args)
73+
74+
75+
def main(args: Optional[List[str]] = None) -> int:
76+
"""Collect the arguments and run."""
77+
if not args:
78+
args = sys.argv[1:]
79+
return run(parse_args(args))
80+
81+
82+
def run(args: argparse.Namespace) -> int:
83+
"""Primary processing loop."""
84+
imports: Set[str] = set()
85+
for document in args.inputs:
86+
_logger.info("Processing %s.", document)
87+
with open(document, "r") as doc_handle:
88+
result = yaml.main.round_trip_load(doc_handle, preserve_quotes=True)
89+
add_lc_filename(result, document)
90+
version = result.get("cwlVersion", None)
91+
if version in ("draft-3", "cwl:draft-3", "v1.0", "v1.1"):
92+
result = cwlupgrader.upgrade_document(
93+
result, False, False, args.dir, imports
94+
)
95+
else:
96+
_logger.error(
97+
"Sorry, %s in %s is not a supported CWL version by this tool.",
98+
(version, document),
99+
)
100+
return -1
101+
uri = Path(document).resolve().as_uri()
102+
if not args.no_expression_refactoring:
103+
refactored, _ = cwl_v1_2_expression_refactor.traverse(
104+
load_document_by_yaml(result, uri),
105+
not args.etools,
106+
False,
107+
args.skip_some1,
108+
args.skip_some2,
109+
)
110+
if not isinstance(refactored, MutableSequence):
111+
result = save(
112+
refactored,
113+
base_url=refactored.loadingOptions.fileuri
114+
if refactored.loadingOptions.fileuri
115+
else "",
116+
)
117+
# ^^ Setting the base_url and keeping the default value
118+
# for relative_uris=True means that the IDs in the generated
119+
# JSON/YAML are kept clean of the path to the input document
120+
else:
121+
result = [
122+
save(result_item, base_url=result_item.loadingOptions.fileuri)
123+
for result_item in refactored
124+
]
125+
if "$graph" in result:
126+
packed = result
127+
else:
128+
with tempfile.TemporaryDirectory() as tmpdirname:
129+
path = Path(tmpdirname) / Path(document).name
130+
with open(path, "w") as handle:
131+
yaml.main.round_trip_dump(result, handle)
132+
# TODO replace the cwltool based packing with a parser_v1_2 based packer
133+
runtimeContext = RuntimeContext()
134+
loadingContext = LoadingContext()
135+
use_standard_schema("v1.2")
136+
# loadingContext.construct_tool_object = workflow.default_make_tool
137+
# loadingContext.resolver = tool_resolver
138+
loadingContext.do_update = False
139+
uri, tool_file_uri = resolve_tool_uri(
140+
str(path),
141+
resolver=loadingContext.resolver,
142+
fetcher_constructor=loadingContext.fetcher_constructor,
143+
)
144+
loadingContext, workflowobj, uri = fetch_document(uri, loadingContext)
145+
loadingContext, uri = resolve_and_validate_document(
146+
loadingContext,
147+
workflowobj,
148+
uri,
149+
preprocess_only=True,
150+
skip_schemas=True,
151+
)
152+
packed = print_pack(loadingContext, uri)
153+
output = Path(args.dir) / Path(document).name
154+
with open(output, "w", encoding="utf-8") as output_filehandle:
155+
output_filehandle.write(packed)
156+
return 0
157+
158+
159+
if __name__ == "__main__":
160+
main()
161+
sys.exit(0)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ requests
22
schema-salad >= 7, < 9
33
typing_extensions
44
cwltool
5+
cwl-upgrader >= 1.2

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"typing_extensions",
2424
"cwltool >= 3.0.20201113183607",
2525
"cwlformat",
26+
"cwl-upgrader >= 1.2",
2627
],
2728
setup_requires=[] + pytest_runner,
2829
tests_require=["pytest<7", "cwltool"],
@@ -32,6 +33,7 @@
3233
"cwl_utils/cwl_expression_refactor.py",
3334
"cwl_utils/cite_extract.py",
3435
"cwl_utils/graph_split.py",
36+
"cwl_utils/cwl_normalizer.py",
3537
],
3638
long_description=open("./README.md").read(),
3739
long_description_content_type="text/markdown",

tests/test_docker_extract.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from pathlib import Path
22
from tempfile import TemporaryDirectory
33

4-
54
import cwl_utils.parser_v1_0 as parser
65
from cwl_utils.docker_extract import traverse
76
from cwl_utils.image_puller import DockerImagePuller, SingularityImagePuller

0 commit comments

Comments
 (0)