Skip to content

Commit c1e8b6a

Browse files
authored
Handled the pickValue in the check_types function to avoid unnecessary warnings. (#397)
1 parent 56fe3a6 commit c1e8b6a

6 files changed

Lines changed: 258 additions & 25 deletions

File tree

cwl_utils/parser/cwl_v1_2_utils.py

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ def check_all_types(
210210
extra_message = (
211211
"pickValue is %s" % sink.pickValue if sink.pickValue is not None else None
212212
)
213+
sink_type = type_dict[sink.id]
213214
match sink:
214215
case cwl.WorkflowOutputParameter():
215216
sourceName = "outputSource"
@@ -220,7 +221,7 @@ def check_all_types(
220221
case _:
221222
continue
222223
if sourceField is not None:
223-
if isinstance(sourceField, MutableSequence):
224+
if isinstance(sourceField, MutableSequence) and len(sourceField) > 1:
224225
linkMerge: str | None = sink.linkMerge or (
225226
"merge_nested" if len(sourceField) > 1 else None
226227
)
@@ -231,23 +232,34 @@ def check_all_types(
231232
srcs_of_sink += [src_dict[parm_id]]
232233
if (
233234
_is_conditional_step(param_to_step, parm_id)
234-
and sink.pickValue is not None
235+
and sink.pickValue is None
235236
):
236-
validation["warning"].append(
237-
SrcSink(
238-
src_dict[parm_id],
239-
sink,
240-
linkMerge,
241-
message="Source is from conditional step, but pickValue is not used",
237+
src_typ = aslist(type_dict[src_dict[parm_id].id])
238+
if "null" not in src_typ:
239+
src_typ = ["null"] + cast(list[Any], src_typ)
240+
if (
241+
not isinstance(sink_type, MutableSequence)
242+
or "null" not in sink_type
243+
):
244+
validation["warning"].append(
245+
SrcSink(
246+
src_dict[parm_id],
247+
sink,
248+
linkMerge,
249+
message="Source is from conditional step, but pickValue is not used",
250+
)
242251
)
243-
)
252+
type_dict[src_dict[parm_id].id] = src_typ
244253
if _is_all_output_method_loop_step(param_to_step, parm_id):
245254
src_typ = type_dict[src_dict[parm_id].id]
246255
type_dict[src_dict[parm_id].id] = cwl.ArraySchema(
247256
items=src_typ, type_="array"
248257
)
249258
else:
250-
parm_id = cast(str, sourceField)
259+
if isinstance(sourceField, MutableSequence):
260+
parm_id = cast(str, sourceField[0])
261+
else:
262+
parm_id = cast(str, sourceField)
251263
if parm_id not in src_dict:
252264
raise SourceLine(sink, sourceName, ValidationException).makeError(
253265
f"{sourceName} not found: {parm_id}"
@@ -289,7 +301,7 @@ def check_all_types(
289301
for src in srcs_of_sink:
290302
check_result = check_types(
291303
type_dict[cast(str, src.id)],
292-
type_dict[sink.id],
304+
sink_type,
293305
linkMerge,
294306
getattr(sink, "valueFrom", None),
295307
)
@@ -313,19 +325,24 @@ def check_types(
313325
"""
314326
if valueFrom is not None:
315327
return "pass"
316-
if linkMerge is None:
317-
if can_assign_src_to_sink(srctype, sinktype, strict=True):
318-
return "pass"
319-
if can_assign_src_to_sink(srctype, sinktype, strict=False):
320-
return "warning"
321-
return "exception"
322-
if linkMerge == "merge_nested":
323-
return check_types(
324-
cwl.ArraySchema(items=srctype, type_="array"), sinktype, None, None
325-
)
326-
if linkMerge == "merge_flattened":
327-
return check_types(merge_flatten_type(srctype), sinktype, None, None)
328-
raise ValidationException(f"Invalid value {linkMerge} for linkMerge field.")
328+
match linkMerge:
329+
case None:
330+
if can_assign_src_to_sink(srctype, sinktype, strict=True):
331+
return "pass"
332+
if can_assign_src_to_sink(srctype, sinktype, strict=False):
333+
return "warning"
334+
return "exception"
335+
case "merge_nested":
336+
return check_types(
337+
cwl.ArraySchema(items=srctype, type_="array"),
338+
sinktype,
339+
None,
340+
None,
341+
)
342+
case "merge_flattened":
343+
return check_types(merge_flatten_type(srctype), sinktype, None, None)
344+
case _:
345+
raise ValidationException(f"Invalid value {linkMerge} for linkMerge field.")
329346

330347

331348
def content_limit_respected_read_bytes(f: IO[bytes]) -> bytes:
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.2
4+
requirements:
5+
ScatterFeatureRequirement: {}
6+
MultipleInputFeatureRequirement: {}
7+
StepInputExpressionRequirement: {}
8+
inputs:
9+
letters0:
10+
type: [string, int]
11+
default: "a0"
12+
letters1:
13+
type: string[]
14+
default: ["a1", "b1"]
15+
letters2:
16+
type: [string, int]
17+
default: "a2"
18+
letters3:
19+
type: string[]
20+
default: ["a3", "b3"]
21+
letters4:
22+
type: string
23+
default: "a4"
24+
letters5:
25+
type: string[]
26+
default: ["a5", "b5", "c5"]
27+
exec:
28+
type: bool
29+
default: False
30+
31+
outputs:
32+
single:
33+
type: File?
34+
outputSource:
35+
- step1/txt
36+
37+
steps:
38+
- id: step1
39+
run: echo.cwl
40+
in:
41+
exec: exec
42+
out: [txt]
43+
when: $(inputs.exec)
44+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.2
4+
requirements:
5+
ScatterFeatureRequirement: {}
6+
MultipleInputFeatureRequirement: {}
7+
StepInputExpressionRequirement: {}
8+
inputs:
9+
letters0:
10+
type: [string, int]
11+
default: "a0"
12+
letters1:
13+
type: string[]
14+
default: ["a1", "b1"]
15+
letters2:
16+
type: [string, int]
17+
default: "a2"
18+
letters3:
19+
type: string[]
20+
default: ["a3", "b3"]
21+
letters4:
22+
type: string
23+
default: "a4"
24+
letters5:
25+
type: string[]
26+
default: ["a5", "b5", "c5"]
27+
exec:
28+
type: bool
29+
default: False
30+
31+
outputs:
32+
single:
33+
type: File
34+
outputSource:
35+
- step1/txt
36+
37+
steps:
38+
- id: step1
39+
run: echo.cwl
40+
in:
41+
exec: exec
42+
out: [txt]
43+
when: $(inputs.exec)
44+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.2
4+
requirements:
5+
ScatterFeatureRequirement: {}
6+
MultipleInputFeatureRequirement: {}
7+
StepInputExpressionRequirement: {}
8+
inputs:
9+
letters0:
10+
type: [string, int]
11+
default: "a0"
12+
letters1:
13+
type: string[]
14+
default: ["a1", "b1"]
15+
letters2:
16+
type: [string, int]
17+
default: "a2"
18+
letters3:
19+
type: string[]
20+
default: ["a3", "b3"]
21+
letters4:
22+
type: string
23+
default: "a4"
24+
letters5:
25+
type: string[]
26+
default: ["a5", "b5", "c5"]
27+
exec:
28+
type: bool
29+
default: False
30+
31+
outputs:
32+
single_a:
33+
type: File
34+
outputSource: step1/txt
35+
36+
steps:
37+
- id: step1
38+
run: echo.cwl
39+
in:
40+
exec: exec
41+
out: [txt]
42+
when: $(inputs.exec)
43+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.2
4+
requirements:
5+
ScatterFeatureRequirement: {}
6+
MultipleInputFeatureRequirement: {}
7+
StepInputExpressionRequirement: {}
8+
inputs:
9+
letters0:
10+
type: [string, int]
11+
default: "a0"
12+
letters1:
13+
type: string[]
14+
default: ["a1", "b1"]
15+
letters2:
16+
type: [string, int]
17+
default: "a2"
18+
letters3:
19+
type: string[]
20+
default: ["a3", "b3"]
21+
letters4:
22+
type: string
23+
default: "a4"
24+
letters5:
25+
type: string[]
26+
default: ["a5", "b5", "c5"]
27+
exec:
28+
type: bool
29+
default: False
30+
31+
outputs:
32+
pair:
33+
type: File[]
34+
outputSource:
35+
- step1/txt
36+
- step2/txt
37+
38+
steps:
39+
- id: step1
40+
run: echo.cwl
41+
in:
42+
exec: exec
43+
out: [txt]
44+
when: $(inputs.exec)
45+
- id: step2
46+
run: echo.cwl
47+
in: []
48+
out: [txt]
49+
when: $(inputs.exec)
50+

cwl_utils/tests/test_parser_utils.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# SPDX-License-Identifier: Apache-2.0
22
"""Test the CWL parsers utility functions."""
3+
import logging
4+
import re
35
import tempfile
46
from collections.abc import MutableSequence
57
from typing import cast
68

79
import pytest
8-
from pytest import raises
10+
from pytest import raises, LogCaptureFixture
911
from schema_salad.exceptions import ValidationException
1012

1113
import cwl_utils.parser.cwl_v1_0
@@ -71,6 +73,39 @@ def test_static_checker_fail(cwlVersion: str) -> None:
7173
cwl_utils.parser.utils.static_checker(cwl_obj7)
7274

7375

76+
def test_static_checker_warning(caplog: LogCaptureFixture) -> None:
77+
uri1 = get_path("testdata/checker_wf/warning-wf.cwl").as_uri()
78+
cwl_obj1 = load_document_by_uri(uri1)
79+
with caplog.at_level(level=logging.WARNING, logger="cwl_utils"):
80+
cwl_utils.parser.utils.static_checker(cwl_obj1)
81+
assert "Source is from conditional step and may produce `null`" in caplog.text
82+
caplog.clear()
83+
84+
uri2 = get_path("testdata/checker_wf/no-warning-wf.cwl").as_uri()
85+
cwl_obj2 = load_document_by_uri(uri2)
86+
with caplog.at_level(level=logging.WARNING, logger="cwl_utils"):
87+
cwl_utils.parser.utils.static_checker(cwl_obj2)
88+
assert not caplog.text
89+
caplog.clear()
90+
91+
uri3 = get_path("testdata/checker_wf/warning-wf2.cwl").as_uri()
92+
cwl_obj3 = load_document_by_uri(uri3)
93+
with caplog.at_level(level=logging.WARNING, logger="cwl_utils"):
94+
cwl_utils.parser.utils.static_checker(cwl_obj3)
95+
assert "Source is from conditional step and may produce `null`" in caplog.text
96+
caplog.clear()
97+
98+
uri4 = get_path("testdata/checker_wf/warning-wf3.cwl").as_uri()
99+
cwl_obj4 = load_document_by_uri(uri4)
100+
with caplog.at_level(level=logging.WARNING, logger="cwl_utils"):
101+
cwl_utils.parser.utils.static_checker(cwl_obj4)
102+
assert re.search(
103+
'with sink \'pair\' of type {"name":.* "items": "File", "type": "array"}',
104+
caplog.text,
105+
)
106+
assert "Source is from conditional step, but pickValue is not used" in caplog.text
107+
108+
74109
@pytest.mark.parametrize("cwlVersion", ["v1_0", "v1_1", "v1_2"])
75110
def test_static_checker_success(cwlVersion: str) -> None:
76111
"""Confirm that static type checker correctly validates workflows."""

0 commit comments

Comments
 (0)