Skip to content

Commit

Permalink
UW-639 Mixed cyclestr / text support (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa authored Sep 12, 2024
1 parent 4e67e51 commit afce2f2
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 41 deletions.
24 changes: 21 additions & 3 deletions docs/sections/user_guide/yaml/rocoto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,30 @@ In the example, the resulting log would appear in the XML file as:

.. code-block:: xml
<log>
<cyclestr>/some/path/to/&FOO;</cyclestr>
</log>
<log><cyclestr>/some/path/to/&FOO;</cyclestr></log>
The ``attrs:`` block is optional within the ``cyclestr:`` block and can be used to specify the cycle offset.

Wherever a ``cyclestr:`` block is accepted, a YAML sequence mixing text and ``cyclestr:`` blocks may also be provided. For example,

.. code-block:: yaml
log:
- cyclestr:
value: "%Y%m%d%H"
- -through-
- cyclestr:
attrs:
offset: "06:00:00"
value: "%Y%m%d%H"
- .log
would be rendered as

.. code-block:: xml
<log><cyclestr>%Y%m%d%H</cyclestr>-through-<cyclestr offset="06:00:00">%Y%m%d%H</cyclestr>.log</log>
Tasks Section
-------------

Expand Down
56 changes: 36 additions & 20 deletions src/uwtools/resources/jsonschema/rocoto.jsonschema
Original file line number Diff line number Diff line change
@@ -1,44 +1,60 @@
{
"$defs": {
"compoundTimeString": {
"anyOf": [
"oneOf": [
{
"type": "integer"
"$ref": "#/$defs/compoundTimeStringElement"
},
{
"type": "string"
"items": {
"$ref": "#/$defs/compoundTimeStringElement"
},
"type": "array"
}
]
},
"compoundTimeStringElement": {
"oneOf": [
{
"$ref": "#/$defs/cycleString"
},
{
"type": "integer"
},
{
"type": "string"
}
]
},
"cycleString": {
"additionalProperties": false,
"properties": {
"cyclestr": {
"additionalProperties": false,
"properties": {
"cyclestr": {
"attrs": {
"additionalProperties": false,
"properties": {
"attrs": {
"additionalProperties": false,
"properties": {
"offset": {
"$ref": "#/$defs/time"
}
},
"type": "object"
},
"value": {
"type": "string"
"offset": {
"$ref": "#/$defs/time"
}
},
"required": [
"value"
],
"type": "object"
},
"value": {
"type": "string"
}
},
"required": [
"cyclestr"
"value"
],
"type": "object"
}
]
},
"required": [
"cyclestr"
],
"type": "object"
},
"dependency": {
"additionalProperties": false,
Expand Down
17 changes: 7 additions & 10 deletions src/uwtools/rocoto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any, Optional, Union

from lxml import etree
from lxml.builder import E # type: ignore
from lxml.etree import Element, SubElement, _Element

from uwtools.config.formats.yaml import YAMLConfig
Expand Down Expand Up @@ -113,16 +114,12 @@ def _add_compound_time_string(self, e: _Element, config: Any, tag: str) -> _Elem
:param tag: Name of child element to add.
:return: The child element.
"""
e = SubElement(e, tag)
if isinstance(config, dict):
self._set_attrs(e, config)
if subconfig := config.get(STR.cyclestr, {}):
cyclestr = SubElement(e, STR.cyclestr)
cyclestr.text = subconfig[STR.value]
self._set_attrs(cyclestr, subconfig)
else:
e.text = str(config)
return e
config = config if isinstance(config, list) else [config]
cyclestr = lambda x: E.cyclestr(x["cyclestr"]["value"], **x["cyclestr"].get("attrs", {}))
items = [cyclestr(x) if isinstance(x, dict) else str(x) for x in [tag, *config]]
child: _Element = E(*items) # pylint: disable=not-callable
e.append(child)
return child

def _add_metatask(self, e: _Element, config: dict, name_attr: str) -> None:
"""
Expand Down
37 changes: 29 additions & 8 deletions src/uwtools/tests/test_rocoto.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import DEFAULT as D
from unittest.mock import PropertyMock, patch

from lxml import etree
from pytest import fixture, mark, raises

from uwtools import rocoto
Expand Down Expand Up @@ -110,22 +111,42 @@ def test_instantiate_from_cfgobj(self, assets):
cfgfile, _ = assets
assert rocoto._RocotoXML(config=YAMLConfig(cfgfile))._root.tag == "workflow"

def test__add_compound_time_string_basic(self, instance, root):
config = "bar"
@mark.parametrize("config", ["bar", 42])
def test__add_compound_time_string_basic(self, config, instance, root):
instance._add_compound_time_string(e=root, config=config, tag="foo")
child = root[0]
assert child.tag == "foo"
assert child.text == "bar"
assert child.text == str(config)

def test__add_compound_time_string_cyclestr(self, instance, root):
config = {"attrs": {"bar": "42"}, "cyclestr": {"attrs": {"baz": "43"}, "value": "qux"}}
config = {"cyclestr": {"attrs": {"baz": "42"}, "value": "qux"}}
instance._add_compound_time_string(e=root, config=config, tag="foo")
child = root[0]
assert child.get("bar") == "42"
cyclestr = child[0]
assert cyclestr.get("baz") == "43"
cyclestr = root[0][0]
assert cyclestr.get("baz") == "42"
assert cyclestr.text == "qux"

def test__add_compound_time_string_list(self, instance, root):
config = [
"cycle-",
{"cyclestr": {"value": "%s"}},
"-valid-",
{"cyclestr": {"value": "%s", "attrs": {"offset": "00:06:00"}}},
".log",
]
xml = "<a>{}</a>".format(
"".join(
[
"cycle-",
"<cyclestr>%s</cyclestr>",
"-valid-",
'<cyclestr offset="00:06:00">%s</cyclestr>',
".log",
]
)
)
instance._add_compound_time_string(e=root, config=config, tag="a")
assert etree.tostring(root[0]).decode("utf-8") == xml

def test__add_metatask(self, instance, root):
config = {
"metatask_foo": "1",
Expand Down

0 comments on commit afce2f2

Please sign in to comment.