Skip to content

Commit d10e886

Browse files
committed
Merge branch 'main' into fs-class-docstring-update
2 parents b965e16 + dc9846d commit d10e886

File tree

12 files changed

+68
-18
lines changed

12 files changed

+68
-18
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"noaa": ("https://www.noaa.gov/%s", "%s"),
5555
"pylint": ("https://pylint.readthedocs.io/en/stable/%s", "%s"),
5656
"pytest": ("https://docs.pytest.org/en/7.4.x/%s", "%s"),
57+
"python": ("https://docs.python.org/3/library/%s", "%s"),
5758
"rocoto": ("https://christopherwharrop.github.io/rocoto/%s", "%s"),
5859
"rst": ("https://www.sphinx-doc.org/en/master/usage/restructuredtext/%s", "%s"),
5960
"rtd": ("https://readthedocs.org/projects/uwtools/%s", "%s"),

docs/sections/user_guide/yaml/tags.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ Or explicit:
2323
2424
Additionally, UW defines the following tags to support use cases not covered by standard tags:
2525

26+
``!datetime``
27+
^^^^^^^^^^^^^
28+
29+
Converts the tagged node to a Python ``datetime`` object. For example, given ``input.yaml``:
30+
31+
.. code-block:: yaml
32+
33+
date1: 2024-09-01
34+
date2: !datetime "{{ date1 }}"
35+
36+
.. code-block:: text
37+
38+
% uw config realize -i ../input.yaml --output-format yaml
39+
date1: 2024-09-01
40+
date2: 2024-09-01 00:00:00
41+
42+
The value provided to the tag must be in :python:`ISO 8601 format<datetime.html#datetime.datetime.fromisoformat>` to be interpreted correctly by the ``!datetime`` tag.
43+
2644
``!float``
2745
^^^^^^^^^^
2846

@@ -62,7 +80,7 @@ Parse the tagged file and include its tags. For example, given ``input.yaml``:
6280

6381
.. code-block:: yaml
6482
65-
values: !INCLUDE [./supplemental.yaml]
83+
values: !include [./supplemental.yaml]
6684
6785
and ``supplemental.yaml``:
6886

src/uwtools/config/jinja2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import os
6+
from datetime import datetime
67
from functools import cached_property
78
from pathlib import Path
89
from typing import Optional, Union
@@ -14,7 +15,7 @@
1415
from uwtools.logging import INDENT, MSGWIDTH, log
1516
from uwtools.utils.file import get_file_format, readable, writable
1617

17-
_ConfigVal = Union[bool, dict, float, int, list, str, UWYAMLConvert, UWYAMLRemove]
18+
_ConfigVal = Union[bool, datetime, dict, float, int, list, str, UWYAMLConvert, UWYAMLRemove]
1819

1920

2021
class J2Template:

src/uwtools/config/support.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
import math
44
from collections import OrderedDict
5+
from datetime import datetime
56
from importlib import import_module
6-
from typing import Type, Union
7+
from typing import Callable, Type, Union
78

89
import yaml
910

1011
from uwtools.exceptions import UWConfigError
1112
from uwtools.logging import log
1213
from uwtools.strings import FORMAT
1314

14-
INCLUDE_TAG = "!INCLUDE"
15+
INCLUDE_TAG = "!include"
1516

1617

1718
# Public functions
@@ -107,15 +108,17 @@ class UWYAMLConvert(UWYAMLTag):
107108
method. See the pyyaml documentation for details.
108109
"""
109110

110-
TAGS = ("!float", "!int")
111+
TAGS = ("!datetime", "!float", "!int")
111112

112-
def convert(self) -> Union[float, int]:
113+
def convert(self) -> Union[datetime, float, int]:
113114
"""
114115
Return the original YAML value converted to the specified type.
115116
116117
Will raise an exception if the value cannot be represented as the specified type.
117118
"""
118-
converters: dict[str, Union[type[float], type[int]]] = dict(zip(self.TAGS, [float, int]))
119+
converters: dict[str, Union[Callable[[str], datetime], type[float], type[int]]] = dict(
120+
zip(self.TAGS, [datetime.fromisoformat, float, int])
121+
)
119122
return converters[self.tag](self.value)
120123

121124

src/uwtools/tests/config/formats/test_base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import datetime as dt
66
import logging
77
import os
8+
from datetime import datetime
89
from unittest.mock import patch
910

1011
import yaml
@@ -97,7 +98,7 @@ def test__parse_include(config):
9798
config.data.update(
9899
{
99100
"config": {
100-
"salad_include": f"!INCLUDE [{include_path}]",
101+
"salad_include": f"!include [{include_path}]",
101102
"meat": "beef",
102103
"dressing": "poppyseed",
103104
}
@@ -155,23 +156,28 @@ def test_dereference(tmp_path):
155156
e:
156157
- !int '42'
157158
- !float '3.14'
159+
- !datetime '{{ D }}'
158160
f:
159161
f1: !int '42'
160162
f2: !float '3.14'
163+
D: 2024-10-10 00:19:00
161164
N: "22"
165+
162166
""".strip()
163167
path = tmp_path / "config.yaml"
164168
with open(path, "w", encoding="utf-8") as f:
165169
print(yaml, file=f)
166170
config = YAMLConfig(path)
167171
with patch.dict(os.environ, {"N": "999"}, clear=True):
168172
config.dereference()
173+
print(config["e"])
169174
assert config == {
170175
"a": 44,
171176
"b": {"c": 33},
172177
"d": "{{ X }}",
173-
"e": [42, 3.14],
178+
"e": [42, 3.14, datetime.fromisoformat("2024-10-10 00:19:00")],
174179
"f": {"f1": 42, "f2": 3.14},
180+
"D": datetime.fromisoformat("2024-10-10 00:19:00"),
175181
"N": "22",
176182
}
177183

src/uwtools/tests/config/test_jinja2.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import logging
77
import os
8+
from datetime import datetime
89
from io import StringIO
910
from textwrap import dedent
1011
from types import SimpleNamespace as ns
@@ -280,7 +281,7 @@ def test_unrendered(s, status):
280281
assert jinja2.unrendered(s) is status
281282

282283

283-
@mark.parametrize("tag", ["!float", "!int"])
284+
@mark.parametrize("tag", ["!datetime", "!float", "!int"])
284285
def test__deref_convert_no(caplog, tag):
285286
log.setLevel(logging.DEBUG)
286287
loader = yaml.SafeLoader(os.devnull)
@@ -290,7 +291,14 @@ def test__deref_convert_no(caplog, tag):
290291
assert regex_logged(caplog, "Conversion failed")
291292

292293

293-
@mark.parametrize("converted,tag,value", [(3.14, "!float", "3.14"), (42, "!int", "42")])
294+
@mark.parametrize(
295+
"converted,tag,value",
296+
[
297+
(datetime(2024, 9, 9, 0, 0), "!datetime", "2024-09-09 00:00:00"),
298+
(3.14, "!float", "3.14"),
299+
(42, "!int", "42"),
300+
],
301+
)
294302
def test__deref_convert_ok(caplog, converted, tag, value):
295303
log.setLevel(logging.DEBUG)
296304
loader = yaml.SafeLoader(os.devnull)

src/uwtools/tests/config/test_support.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import logging
77
from collections import OrderedDict
8+
from datetime import datetime
89

910
import yaml
1011
from pytest import fixture, mark, raises
@@ -87,6 +88,18 @@ def loader(self):
8788
# demonstrate that those nodes' convert() methods return representations in type type specified
8889
# by the tag.
8990

91+
def test_datetime_no(self, loader):
92+
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!datetime", value="foo"))
93+
with raises(ValueError):
94+
ts.convert()
95+
96+
def test_datetime_ok(self, loader):
97+
ts = support.UWYAMLConvert(
98+
loader, yaml.ScalarNode(tag="!datetime", value="2024-08-09 12:22:42")
99+
)
100+
assert ts.convert() == datetime(2024, 8, 9, 12, 22, 42)
101+
self.comp(ts, "!datetime '2024-08-09 12:22:42'")
102+
90103
def test_float_no(self, loader):
91104
ts = support.UWYAMLConvert(loader, yaml.ScalarNode(tag="!float", value="foo"))
92105
with raises(ValueError):
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[config]
2-
salad_include = !INCLUDE [./fruit_config.ini]
2+
salad_include = !include [./fruit_config.ini]
33
meat = beef
44
dressing = poppyseed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
&config
2-
salad_include = '!INCLUDE [./fruit_config.nml]'
2+
salad_include = '!include [./fruit_config.nml]'
33
meat = beef
44
dressing = poppyseed
55
/
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
salad_include="!INCLUDE [./fruit_config.sh]"
1+
salad_include="!include [./fruit_config.sh]"
22
meat=beef
33
dressing=poppyseed

0 commit comments

Comments
 (0)