22"""Transforms draft-3 CWL documents into v1.0 as idiomatically as possible."""
33
44from __future__ import print_function
5- from collections import Mapping , MutableMapping , Sequence
5+ from collections import Mapping , MutableMapping , MutableSequence , Sequence
66import sys
77import copy
88from typing import (Any , Dict , List , Optional , # pylint:disable=unused-import
@@ -18,16 +18,33 @@ def main(args=None): # type: (Optional[List[str]]) -> int
1818 for path in args :
1919 with open (path ) as entry :
2020 document = ruamel .yaml .safe_load (entry )
21- if ('cwlVersion' in document
22- and (document ['cwlVersion' ] == 'cwl:draft-3'
23- or document ['cwlVersion' ] == 'draft-3' )):
24- document = draft3_to_v1_0 (document )
21+ if 'cwlVersion' in document :
22+ if (document ['cwlVersion' ] == 'cwl:draft-3'
23+ or document ['cwlVersion' ] == 'draft-3' ):
24+ document = draft3_to_v1_0 (document )
25+ elif document ['cwlVersion' ] == 'v1.0' :
26+ document = v1_0_to_v1_1 (document )
2527 else :
2628 print ("Skipping non draft-3 CWL document" , file = sys .stderr )
2729 print (ruamel .yaml .round_trip_dump (
2830 document , default_flow_style = False ))
2931 return 0
3032
33+ def v1_0_to_v1_1 (document ): # type: (Dict[Text, Any]) -> Dict
34+ """CWL v1.0.x to v1.1 transformation loop."""
35+ # TODO: handle $import, see imported-hint.cwl schemadef-tool.cwl schemadef-wf.cwl
36+ _v1_0_to_v1_1 (document )
37+ if isinstance (document , MutableMapping ):
38+ for key , value in document .items ():
39+ if isinstance (value , MutableMapping ):
40+ document [key ] = _v1_0_to_v1_1 (value )
41+ elif isinstance (value , list ):
42+ for index , entry in enumerate (value ):
43+ if isinstance (entry , MutableMapping ):
44+ value [index ] = _v1_0_to_v1_1 (entry )
45+ document ['cwlVersion' ] = 'v1.1'
46+ return sort_v1_0 (document )
47+
3148
3249def draft3_to_v1_0 (document ): # type: (Dict[Text, Any]) -> Dict
3350 """Transformation loop."""
@@ -67,6 +84,102 @@ def _draft3_to_v1_0(document):
6784
6885 return document
6986
87+ WORKFLOW_INPUT_INPUTBINDING = \
88+ "{}[cwl-upgrader_v1_0_to_v1_1] Original input had the follow (unused) inputBinding element: {}"
89+
90+ V1_0_TO_V1_1_REWRITE = {
91+ "http://commonwl.org/cwltool#WorkReuse" : "WorkReuse" ,
92+ "http://arvados.org/cwl#ReuseRequirement" : "WorkReuse" ,
93+ "http://commonwl.org/cwltool#TimeLimit" : "ToolTimeLimit" ,
94+ "http://commonwl.org/cwltool#NetworkAccess" : "NetworkAccess" ,
95+ "http://commonwl.org/cwltool#InplaceUpdateRequirement" : "InplaceUpdateRequirement" ,
96+ "http://commonwl.org/cwltool#LoadListingRequirement" : "LoadListingRequirement"
97+ }
98+
99+
100+ def _v1_0_to_v1_1 (document ):
101+ # type: (MutableMapping[Text, Any]) -> MutableMapping[Text, Any]
102+ """Inner loop for transforming draft-3 to v1.0."""
103+ if "class" in document :
104+ if document ['class' ] == 'Workflow' :
105+ upgrade_v1_0_hints_and_reqs (document )
106+ cleanup_v1_0_input_bindings (document )
107+ steps = document ['steps' ]
108+ if isinstance (steps , MutableSequence ):
109+ for entry in steps :
110+ upgrade_v1_0_hints_and_reqs (entry )
111+ elif isinstance (steps , MutableMapping ):
112+ for step_name in steps :
113+ upgrade_v1_0_hints_and_reqs (steps [step_name ])
114+ elif document ['class' ] == 'CommandLineTool' :
115+ upgrade_v1_0_hints_and_reqs (document )
116+ network_access = has_hint_or_req (document , "NetworkAccess" )
117+ listing = has_hint_or_req (document , "LoadListingRequirement" )
118+ if 'hints' in document :
119+ if isinstance (document ['hints' ], MutableSequence ):
120+ if not network_access :
121+ document ['hints' ].append ({"class" : "NetworkAcess" , "networkAccess" : True })
122+ if not listing :
123+ document ['hints' ].append ({"class" : "LoadListingRequirement" , "loadListing" : "deep_listing" })
124+ elif isinstance (document ['hints' ], MutableMapping ):
125+ if not network_access :
126+ document ['hints' ]["NetworkAcess" ] = {"networkAccess" : True }
127+ if not listing :
128+ document ['hints' ]["LoadListingRequirement" ] = {"loadListing" : "deep_listing" }
129+ elif document ['class' ] == 'ExpressionTool' :
130+ cleanup_v1_0_input_bindings (document )
131+ return document
132+
133+
134+ def cleanup_v1_0_input_bindings (document ):
135+ """In v1.1 only loadContents is allow in inputs for a Workflow or ExpressionTool."""
136+ def cleanup (inp ):
137+ """Serialize non loadContents fields and add that to the doc."""
138+ if 'inputBinding' in inp :
139+ bindings = inp ["inputBinding" ]
140+ for field in list (bindings .keys ()):
141+ if field != "loadContents" :
142+ prefix = '' if 'doc' not in inp else '{}\n ' .format (inp ['doc' ])
143+ inp ['doc' ] = WORKFLOW_INPUT_INPUTBINDING .format (prefix , field )
144+ del bindings [field ]
145+ if not bindings :
146+ del inp ['inputBinding' ]
147+ inputs = document ['inputs' ]
148+ if isinstance (inputs , MutableSequence ):
149+ for entry in inputs :
150+ cleanup (entry )
151+ elif isinstance (inputs , MutableMapping ):
152+ for input_name in inputs :
153+ cleanup (inputs [input_name ])
154+
155+
156+ def upgrade_v1_0_hints_and_reqs (document ):
157+ for extra in ("requirements" , "hints" ):
158+ if extra in document :
159+ if isinstance (document [extra ], MutableMapping ):
160+ for req_name in document [extra ]:
161+ if req_name in V1_0_TO_V1_1_REWRITE :
162+ extra [V1_0_TO_V1_1_REWRITE [req_name ]] = extra .pop (req_name )
163+ elif isinstance (document [extra ], MutableSequence ):
164+ for entry in document [extra ]:
165+ if entry ['class' ] in V1_0_TO_V1_1_REWRITE :
166+ entry ['class' ] = V1_0_TO_V1_1_REWRITE [entry ['id' ]]
167+ else :
168+ raise Exception ("{} section must be either a list of dictionaries or a dictionary of dictionaries!: {}" ).format (extra , document [extra ])
169+
170+ def has_hint_or_req (document , name ):
171+ """Detects an existing named hint or requirement."""
172+ for extra in ("requirements" , "hints" ):
173+ if extra in document :
174+ if isinstance (document [extra ], MutableMapping ):
175+ if name in document [extra ]:
176+ return True
177+ elif isinstance (document [extra ], MutableSequence ):
178+ for entry in document [extra ]:
179+ if entry ['class' ] == name :
180+ return True
181+ return False
182+
70183
71184def workflow_clean (document ): # type: (MutableMapping[Text, Any]) -> None
72185 """Transform draft-3 style Workflows to more idiomatic v1.0"""
0 commit comments