Skip to content

Commit

Permalink
Merge pull request #796 from crim-ca/patch-default-literal
Browse files Browse the repository at this point in the history
  • Loading branch information
fmigneault authored Feb 19, 2025
2 parents 343d906 + 1b5eb06 commit 760c115
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 2 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ Changes

Changes:
--------
- No change.
- Add resilient handling of `I/O` literal ``default`` values when parsing remote `OGC API - Processes` descriptions.
Due to varying definitions from the standard revisions, some implementations could indicate a single literal default
value as an array representation (e.g.: ``default: [1.23]``), leading to parsing "errors" in `Weaver` that expects a
strict match between the ``default`` value and its ``type``.

Fixes:
------
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ exclude =
env,
parts,
examples,
reports,
node_modules,

[doc8]
Expand Down
86 changes: 86 additions & 0 deletions tests/processes/test_convert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
Unit tests of functions within :mod:`weaver.processes.convert`.
"""
import inspect
import logging

# pylint: disable=R1729 # ignore non-generator representation employed for displaying test log results

import json
Expand Down Expand Up @@ -1006,6 +1009,89 @@ def test_cwl2wps_io_raise_mixed_types(test_type):
cwl2wps_io(io_info, IO_INPUT)


@pytest.mark.parametrize(
["io_default", "io_expect", "is_erroneous"],
[
(
{"name": "data", "type": "string", "default": ["test"]},
"test",
True,
),
(
{"name": "data", "type": "integer", "default": [1]},
1,
True,
),
(
{"name": "data", "type": "float", "default": [3.1416]},
3.1416,
True,
),
(
{"name": "data", "type": "boolean", "default": [True]},
True,
True,
),
(
# empty considered as "null default"
{"name": "data", "type": "integer", "default": []},
None,
True,
),
(
# explicitly "null default"
{"name": "data", "type": "integer", "default": [None]},
None,
True,
),
(
# only 1 array element allowed for silent handling
{"name": "data", "type": "integer", "default": [1, 2, 3]},
PackageTypeError,
True,
),
(
# don't allow nested lists, even if literal inside
# assume it is too much "lists" involved, and the intention is most probably wrong at this point
{"name": "data", "type": "integer", "default": [[1]]},
PackageTypeError,
True,
),
(
# don't allow non-literals within list, even if single element
{"name": "data", "type": "integer", "default": [{}]},
PackageTypeError,
True,
),
(
# sanity check valid case
{"name": "data", "type": "string", "default": "test"},
"test",
False,
),
(
# sanity check valid case
{"name": "data", "type": "float", "default": 3.1416},
3.1416,
False,
)
]
)
def test_cwl2wps_io_literal_handle_erroneous_defaults(io_default, io_expect, is_erroneous, caplog):
"""
Given a remote :term:`OAP` process description with an erroneous ``default``, ensure parsing can mitigate silently.
"""

if inspect.isclass(io_expect) and issubclass(io_expect, Exception):
with pytest.raises(io_expect):
cwl2wps_io(io_default, IO_INPUT)
else:
with caplog.at_level(logging.WARNING):
result = cwl2wps_io(io_default, IO_INPUT)
assert result.data == io_expect
assert not is_erroneous or (is_erroneous and "detected badly formed 'default'" in caplog.text.lower())


def test_cwl2wps_io_record_format():
"""
Validate handling of alternative representation by CWL I/O record after parsing contents into a tool instance.
Expand Down
20 changes: 19 additions & 1 deletion weaver/processes/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,25 @@ def cwl2wps_io(io_info, io_select):
# anything later on when CWL/WPS merging is attempted
if io_def.symbols is not AnyValue:
kw["allowed_values"] = io_def.symbols
kw["default"] = io_info.get("default", None)
io_default = io_info.get("default", None)
# resilience against badly formed process descriptions
# (in case the reference is a remote process we have no control over)
if isinstance(io_default, list):
LOGGER.warning(
"Detected badly formed 'default' as array for literal input [%s]. "
"Attempting patch of the definition to make it valid.",
io_def.name,
)
if len(io_default) == 0:
io_default = None
elif len(io_default) == 1:
io_default = io_default[0]
if not isinstance(io_default, (str, float, int, bool, type(None))):
raise PackageTypeError(
f"Unsupported I/O info definition: '{io_info!r}' with '{io_select}' "
f"specifies a 'default' that does not correspond to a valid literal value."
)
kw["default"] = io_default
kw["min_occurs"] = io_def.min_occurs
kw["max_occurs"] = io_def.max_occurs
return io_literal(**kw)
Expand Down

0 comments on commit 760c115

Please sign in to comment.