Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa committed Jan 3, 2025
1 parent 75001ff commit dc232f3
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 35 deletions.
72 changes: 44 additions & 28 deletions src/uwtools/config/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,30 +90,7 @@ def yaml_to_str(d: dict, sort: bool = False) -> str:
return yaml.dump(d, default_flow_style=False, indent=2, sort_keys=sort, width=math.inf).strip()


class UWYAMLTag:
"""
A base class for custom UW YAML tags.
"""

def __init__(self, _: yaml.SafeLoader, node: yaml.nodes.Node) -> None:
self.tag: str = node.tag
self.value: str = node.value

def __repr__(self) -> str:
return ("%s %s" % (self.tag, self.value)).strip()

@staticmethod
def represent(dumper: yaml.Dumper, data: UWYAMLTag) -> yaml.nodes.Node:
"""
Serialize a tagged scalar as "!type value".
Implements the interface required by pyyaml's add_representer() function. See the pyyaml
documentation for details.
"""
return dumper.represent_scalar(data.tag, data.value)


class UWYAMLConvert(UWYAMLTag):
class UWYAMLConvert:
"""
A class supporting custom YAML tags specifying type conversions.
Expand All @@ -124,13 +101,24 @@ class UWYAMLConvert(UWYAMLTag):
TAGS = ("!bool", "!datetime", "!dict", "!float", "!int", "!list")
ValT = Union[bool, datetime, dict, float, int, list]

def __init__(self, _: yaml.SafeLoader, node: yaml.nodes.Node) -> None:
self.tag: str = node.tag
self.value: str = node.value

def __repr__(self) -> str:
return ("%s %s" % (self.tag, self.convert())).strip()

def convert(self) -> UWYAMLConvert.ValT:
return self._convert(self)

@staticmethod
def _convert(data: UWYAMLConvert) -> UWYAMLConvert.ValT:
"""
Return the original YAML value converted to the type speficied by the tag.
Will raise an exception if the value cannot be represented as the specified type.
:raises: Appropriate exception if the value cannot be represented as the required type.
"""
load_as = lambda t, v: t(yaml.safe_load(str(v)))
load_as = lambda t, v: t(yaml.safe_load(v))
converters: list[Callable[..., UWYAMLConvert.ValT]] = [
partial(load_as, bool),
datetime.fromisoformat,
Expand All @@ -139,10 +127,21 @@ def convert(self) -> UWYAMLConvert.ValT:
int,
partial(load_as, list),
]
return dict(zip(self.TAGS, converters))[self.tag](self.value)
return dict(zip(UWYAMLConvert.TAGS, converters))[data.tag](data.value)

@staticmethod
def represent(dumper: yaml.Dumper, data: UWYAMLConvert) -> yaml.nodes.Node:
"""
Serialize a tagged scalar as "!type value".
class UWYAMLRemove(UWYAMLTag):
Implements the interface required by pyyaml's add_representer() function. See the pyyaml
documentation for details.
"""
# return dumper.represent_scalar(data.tag, str(data.convert()))
return dumper.represent_scalar(data.tag, data.value)


class UWYAMLRemove:
"""
A class supporting a custom YAML tag to remove a YAML key/value pair.
Expand All @@ -151,3 +150,20 @@ class UWYAMLRemove(UWYAMLTag):
"""

TAGS = ("!remove",)

def __init__(self, _: yaml.SafeLoader, node: yaml.nodes.Node) -> None:
self.tag: str = node.tag
self.value: str = node.value

def __repr__(self) -> str:
return ("%s %s" % (self.tag, self.value)).strip()

@staticmethod
def represent(dumper: yaml.Dumper, data: UWYAMLRemove) -> yaml.nodes.Node:
"""
Serialize a tagged scalar as "!type value".
Implements the interface required by pyyaml's add_representer() function. See the pyyaml
documentation for details.
"""
return dumper.represent_scalar(data.tag, data.value)
12 changes: 6 additions & 6 deletions src/uwtools/tests/config/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def comp(self, ts: support.UWYAMLConvert, s: str):

@fixture
def loader(self):
yaml.add_representer(support.UWYAMLConvert, support.UWYAMLTag.represent)
yaml.add_representer(support.UWYAMLConvert, support.UWYAMLConvert.represent)
return yaml.SafeLoader("")

# These tests bypass YAML parsing, constructing nodes with explicit string values. They then
Expand Down Expand Up @@ -111,9 +111,9 @@ def test_dict_no(self, loader):
ts.convert()

def test_dict_ok(self, loader):
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!dict", value="{a0: 0, a1: 1}"))
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!dict", value="{a0: 0,a1: 1,}"))
assert ts.convert() == {"a0": 0, "a1": 1}
self.comp(ts, "!dict '{a0: 0, a1: 1}'")
self.comp(ts, "!dict '{a0: 0,a1: 1,}'")

def test_float_no(self, loader):
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!float", value="foo"))
Expand Down Expand Up @@ -141,9 +141,9 @@ def test_list_no(self, loader):
ts.convert()

def test_list_ok(self, loader):
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!list", value="[1, 2, 3,]"))
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!list", value="[1,2,3,]"))
assert ts.convert() == [1, 2, 3]
self.comp(ts, "!list '[1, 2, 3,]'")
self.comp(ts, "!list '[1,2,3,]'")

def test___repr__(self, loader):
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!int", value="42"))
Expand All @@ -156,6 +156,6 @@ class Test_UWYAMLRemove:
"""

def test___repr__(self):
yaml.add_representer(support.UWYAMLRemove, support.UWYAMLTag.represent)
yaml.add_representer(support.UWYAMLRemove, support.UWYAMLRemove.represent)
node = support.UWYAMLRemove(yaml.SafeLoader(""), yaml.ScalarNode(tag="!remove", value=""))
assert str(node) == "!remove"
2 changes: 1 addition & 1 deletion src/uwtools/tests/config/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def test_realize_config_double_tag_nest(tmp_path):
help_realize_config_double_tag(config, expected, tmp_path)


def test_realize_config_double_tag_nest_forwrad_reference(tmp_path):
def test_realize_config_double_tag_nest_forward_reference(tmp_path):
config = """
a: true
b: false
Expand Down

0 comments on commit dc232f3

Please sign in to comment.