1
1
"""Static checking of CWL workflow connectivity."""
2
2
3
- from collections import namedtuple
4
3
from collections .abc import Iterator , MutableMapping , MutableSequence , Sized
5
- from typing import Any , Literal , Optional , Union , cast
4
+ from typing import Any , Literal , NamedTuple , Optional , Union , cast
6
5
7
6
from schema_salad .exceptions import ValidationException
8
7
from schema_salad .sourceline import SourceLine , bullets , strip_dup_lineno
@@ -184,8 +183,8 @@ def static_checker(
184
183
for param in workflow_inputs + step_outputs :
185
184
src_dict [cast (str , param ["id" ])] = param
186
185
187
- step_inputs_val = check_all_types (src_dict , step_inputs , "source" , param_to_step )
188
- workflow_outputs_val = check_all_types (
186
+ step_inputs_val = _check_all_types (src_dict , step_inputs , "source" , param_to_step )
187
+ workflow_outputs_val = _check_all_types (
189
188
src_dict , workflow_outputs , "outputSource" , param_to_step
190
189
)
191
190
@@ -199,27 +198,34 @@ def static_checker(
199
198
sink = warning .sink
200
199
linkMerge = warning .linkMerge
201
200
sinksf = sorted (
202
- p ["pattern" ] for p in sink .get ("secondaryFiles" , []) if p .get ("required" , True )
201
+ cast (str , p ["pattern" ])
202
+ for p in cast (MutableSequence [CWLObjectType ], sink .get ("secondaryFiles" , []))
203
+ if p .get ("required" , True )
204
+ )
205
+ srcsf = sorted (
206
+ cast (str , p ["pattern" ])
207
+ for p in cast (MutableSequence [CWLObjectType ], src .get ("secondaryFiles" , []))
203
208
)
204
- srcsf = sorted (p ["pattern" ] for p in src .get ("secondaryFiles" , []))
205
209
# Every secondaryFile required by the sink, should be declared
206
210
# by the source
207
211
missing = missing_subset (srcsf , sinksf )
212
+ src_name = shortname (cast (str , src ["id" ]))
213
+ sink_id = cast (str , sink ["id" ])
214
+ sink_name = shortname (sink_id )
208
215
if missing :
209
216
msg1 = "Parameter '{}' requires secondaryFiles {} but" .format (
210
- shortname ( sink [ "id" ]) ,
217
+ sink_name ,
211
218
missing ,
212
219
)
213
220
msg3 = SourceLine (src , "id" ).makeError (
214
- "source '%s' does not provide those secondaryFiles." % (shortname ( src [ "id" ]) )
221
+ "source '%s' does not provide those secondaryFiles." % (src_name )
215
222
)
216
223
msg4 = SourceLine (src .get ("_tool_entry" , src ), "secondaryFiles" ).makeError (
217
224
"To resolve, add missing secondaryFiles patterns to definition of '%s' or"
218
- % (shortname ( src [ "id" ]) )
225
+ % (src_name )
219
226
)
220
227
msg5 = SourceLine (sink .get ("_tool_entry" , sink ), "secondaryFiles" ).makeError (
221
- "mark missing secondaryFiles in definition of '%s' as optional."
222
- % shortname (sink ["id" ])
228
+ "mark missing secondaryFiles in definition of '%s' as optional." % (sink_name )
223
229
)
224
230
msg = SourceLine (sink ).makeError (
225
231
"{}\n {}" .format (msg1 , bullets ([msg3 , msg4 , msg5 ], " " ))
@@ -229,13 +235,13 @@ def static_checker(
229
235
msg = SourceLine (sink , "type" ).makeError (
230
236
"'%s' is not an input parameter of %s, expected %s"
231
237
% (
232
- shortname ( sink [ "id" ]) ,
233
- param_to_step [sink [ "id" ] ]["run" ],
238
+ sink_name ,
239
+ param_to_step [sink_id ]["run" ],
234
240
", " .join (
235
241
shortname (cast (str , s ["id" ]))
236
242
for s in cast (
237
243
list [dict [str , Union [str , bool ]]],
238
- param_to_step [sink [ "id" ] ]["inputs" ],
244
+ param_to_step [sink_id ]["inputs" ],
239
245
)
240
246
if not s .get ("not_connected" )
241
247
),
@@ -247,12 +253,11 @@ def static_checker(
247
253
msg = (
248
254
SourceLine (src , "type" ).makeError (
249
255
"Source '%s' of type %s may be incompatible"
250
- % (shortname ( src [ "id" ]) , json_dumps (src ["type" ]))
256
+ % (src_name , json_dumps (src ["type" ]))
251
257
)
252
258
+ "\n "
253
259
+ SourceLine (sink , "type" ).makeError (
254
- " with sink '%s' of type %s"
255
- % (shortname (sink ["id" ]), json_dumps (sink ["type" ]))
260
+ " with sink '%s' of type %s" % (sink_name , json_dumps (sink ["type" ]))
256
261
)
257
262
)
258
263
if linkMerge is not None :
@@ -274,12 +279,12 @@ def static_checker(
274
279
msg = (
275
280
SourceLine (src , "type" ).makeError (
276
281
"Source '%s' of type %s is incompatible"
277
- % (shortname (src ["id" ]), json_dumps (src ["type" ]))
282
+ % (shortname (cast ( str , src ["id" ]) ), json_dumps (src ["type" ]))
278
283
)
279
284
+ "\n "
280
285
+ SourceLine (sink , "type" ).makeError (
281
286
" with sink '{}' of type {}" .format (
282
- shortname (sink ["id" ]), json_dumps (sink ["type" ])
287
+ shortname (cast ( str , sink ["id" ]) ), json_dumps (sink ["type" ])
283
288
)
284
289
)
285
290
)
@@ -291,16 +296,17 @@ def static_checker(
291
296
exception_msgs .append (msg )
292
297
293
298
for sink in step_inputs :
299
+ sink_type = cast (Union [str , list [str ], list [CWLObjectType ], CWLObjectType ], sink ["type" ])
294
300
if (
295
- "null" != sink [ "type" ]
296
- and "null" not in sink [ "type" ]
301
+ "null" != sink_type
302
+ and "null" not in sink_type
297
303
and "source" not in sink
298
304
and "default" not in sink
299
305
and "valueFrom" not in sink
300
306
):
301
307
msg = SourceLine (sink ).makeError (
302
308
"Required parameter '%s' does not have source, default, or valueFrom expression"
303
- % shortname (sink ["id" ])
309
+ % shortname (cast ( str , sink ["id" ]) )
304
310
)
305
311
exception_msgs .append (msg )
306
312
@@ -313,23 +319,29 @@ def static_checker(
313
319
raise ValidationException (all_exception_msg )
314
320
315
321
316
- SrcSink = namedtuple ("SrcSink" , ["src" , "sink" , "linkMerge" , "message" ])
322
+ class _SrcSink (NamedTuple ):
323
+ """An error or warning message about a connection between two points of the workflow graph."""
324
+
325
+ src : CWLObjectType
326
+ sink : CWLObjectType
327
+ linkMerge : Optional [str ]
328
+ message : Optional [str ]
317
329
318
330
319
- def check_all_types (
331
+ def _check_all_types (
320
332
src_dict : dict [str , CWLObjectType ],
321
333
sinks : MutableSequence [CWLObjectType ],
322
334
sourceField : Union [Literal ["source" ], Literal ["outputSource" ]],
323
335
param_to_step : dict [str , CWLObjectType ],
324
- ) -> dict [str , list [SrcSink ]]:
336
+ ) -> dict [str , list [_SrcSink ]]:
325
337
"""
326
338
Given a list of sinks, check if their types match with the types of their sources.
327
339
328
340
:raises WorkflowException: if there is an unrecognized linkMerge value
329
341
(from :py:func:`check_types`)
330
342
:raises ValidationException: if a sourceField is missing
331
343
"""
332
- validation : dict [str , list [SrcSink ]] = {"warning" : [], "exception" : []}
344
+ validation : dict [str , list [_SrcSink ]] = {"warning" : [], "exception" : []}
333
345
for sink in sinks :
334
346
if sourceField in sink :
335
347
valueFrom = cast (Optional [str ], sink .get ("valueFrom" ))
@@ -356,7 +368,7 @@ def check_all_types(
356
368
srcs_of_sink += [src_dict [parm_id ]]
357
369
if is_conditional_step (param_to_step , parm_id ) and pickValue is None :
358
370
validation ["warning" ].append (
359
- SrcSink (
371
+ _SrcSink (
360
372
src_dict [parm_id ],
361
373
sink ,
362
374
linkMerge ,
@@ -380,7 +392,7 @@ def check_all_types(
380
392
381
393
if pickValue is not None :
382
394
validation ["warning" ].append (
383
- SrcSink (
395
+ _SrcSink (
384
396
src_dict [parm_id ],
385
397
sink ,
386
398
linkMerge ,
@@ -399,7 +411,7 @@ def check_all_types(
399
411
Union [list [str ], CWLObjectType ], snk_typ
400
412
): # Given our type names this works even if not a list
401
413
validation ["warning" ].append (
402
- SrcSink (
414
+ _SrcSink (
403
415
src_dict [parm_id ],
404
416
sink ,
405
417
linkMerge ,
@@ -419,11 +431,11 @@ def check_all_types(
419
431
check_result = check_types (src , sink , linkMerge , valueFrom )
420
432
if check_result == "warning" :
421
433
validation ["warning" ].append (
422
- SrcSink (src , sink , linkMerge , message = extra_message )
434
+ _SrcSink (src , sink , linkMerge , message = extra_message )
423
435
)
424
436
elif check_result == "exception" :
425
437
validation ["exception" ].append (
426
- SrcSink (src , sink , linkMerge , message = extra_message )
438
+ _SrcSink (src , sink , linkMerge , message = extra_message )
427
439
)
428
440
429
441
return validation
0 commit comments