Skip to content

Commit

Permalink
Omnibus 2024-03-01 (ufs-community#424)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa authored Mar 1, 2024
1 parent 654b698 commit ce389ed
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 152 deletions.
11 changes: 4 additions & 7 deletions src/uwtools/api/fv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from pathlib import Path
from typing import Dict, Optional

import iotaa as _iotaa

from uwtools.drivers import support
from uwtools.drivers.fv3 import FV3


Expand All @@ -30,7 +29,7 @@ def execute(
:param batch: Submit run to the batch system
:param dry_run: Do not run forecast, just report what would have been done
:param graph_file: Write Graphviz DOT output here
:return: True if task completes without raising an exception
:return: ``True`` if task completes without raising an exception
"""
obj = FV3(config_file=config_file, cycle=cycle, batch=batch, dry_run=dry_run)
getattr(obj, task)()
Expand All @@ -44,13 +43,11 @@ def graph() -> str:
"""
Returns Graphviz DOT code for the most recently executed task.
"""
return _iotaa.graph()
return support.graph()


def tasks() -> Dict[str, str]:
"""
Returns a mapping from task names to their one-line descriptions.
"""
return {
task: getattr(FV3, task).__doc__.strip().split("\n")[0] for task in _iotaa.tasknames(FV3)
}
return support.tasks(FV3)
12 changes: 4 additions & 8 deletions src/uwtools/api/sfc_climo_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from pathlib import Path
from typing import Dict, Optional

import iotaa as _iotaa

from uwtools.drivers import support
from uwtools.drivers.sfc_climo_gen import SfcClimoGen


Expand All @@ -27,7 +26,7 @@ def execute(
:param batch: Submit run to the batch system
:param dry_run: Do not run forecast, just report what would have been done
:param graph_file: Write Graphviz DOT output here
:return: True if task completes without raising an exception
:return: ``True`` if task completes without raising an exception
"""
obj = SfcClimoGen(config_file=config_file, batch=batch, dry_run=dry_run)
getattr(obj, task)()
Expand All @@ -41,14 +40,11 @@ def graph() -> str:
"""
Returns Graphviz DOT code for the most recently executed task.
"""
return _iotaa.graph()
return support.graph()


def tasks() -> Dict[str, str]:
"""
Returns a mapping from task names to their one-line descriptions.
"""
return {
task: getattr(SfcClimoGen, task).__doc__.strip().split("\n")[0]
for task in _iotaa.tasknames(SfcClimoGen)
}
return support.tasks(SfcClimoGen)
52 changes: 16 additions & 36 deletions src/uwtools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,19 @@ def main() -> None:
# are known, then dispatch to the [sub]mode handler.

setup_logging(quiet=True)
try:
args, checks = _parse_args(sys.argv[1:])
for check in checks[args[STR.mode]][args[STR.action]]:
check(args)
setup_logging(quiet=args[STR.quiet], verbose=args[STR.verbose])
log.debug("Command: %s %s", Path(sys.argv[0]).name, " ".join(sys.argv[1:]))
modes = {
STR.config: _dispatch_config,
STR.fv3: _dispatch_fv3,
STR.rocoto: _dispatch_rocoto,
STR.sfcclimogen: _dispatch_sfc_climo_gen,
STR.template: _dispatch_template,
}
sys.exit(0 if modes[args[STR.mode]](args) else 1)
except Exception as e: # pylint: disable=broad-exception-caught
if _switch(STR.debug) in sys.argv:
log.exception(str(e))
_abort(str(e))
args, checks = _parse_args(sys.argv[1:])
for check in checks[args[STR.mode]][args[STR.action]]:
check(args)
setup_logging(quiet=args[STR.quiet], verbose=args[STR.verbose])
log.debug("Command: %s %s", Path(sys.argv[0]).name, " ".join(sys.argv[1:]))
modes = {
STR.config: _dispatch_config,
STR.fv3: _dispatch_fv3,
STR.rocoto: _dispatch_rocoto,
STR.sfcclimogen: _dispatch_sfc_climo_gen,
STR.template: _dispatch_template,
}
sys.exit(0 if modes[args[STR.mode]](args) else 1)


# Mode config
Expand Down Expand Up @@ -531,16 +526,6 @@ def _add_arg_cycle(group: Group) -> None:
)


def _add_arg_debug(group: Group) -> None:
group.add_argument(
_switch(STR.debug),
action="store_true",
help="""
Print all log messages, plus any unhandled exception's stack trace (implies --verbose)
""",
)


def _add_arg_dry_run(group: Group) -> None:
group.add_argument(
_switch(STR.dryrun),
Expand Down Expand Up @@ -731,12 +716,11 @@ def _abort(msg: str) -> None:

def _add_args_verbosity(group: Group) -> ActionChecks:
"""
Add debug, quiet, and verbose arguments.
Add quiet and verbose arguments.
:param group: The group to add the arguments to.
:return: Check for mutual exclusivity of quiet/verbose arguments.
"""
_add_arg_debug(group)
_add_arg_quiet(group)
_add_arg_verbose(group)
return [_check_verbosity]
Expand Down Expand Up @@ -802,11 +786,8 @@ def _check_template_render_vals_args(args: Args) -> Args:


def _check_verbosity(args: Args) -> Args:
if args.get(STR.quiet) and (args.get(STR.debug) or args.get(STR.verbose)):
_abort(
"%s may not be used with %s or %s"
% (_switch(STR.quiet), _switch(STR.debug), _switch(STR.verbose))
)
if args.get(STR.quiet) and args.get(STR.verbose):
_abort("%s may not be used with %s" % (_switch(STR.quiet), _switch(STR.verbose)))
return args


Expand Down Expand Up @@ -872,7 +853,6 @@ class STR:
compare: str = "compare"
config: str = "config"
cycle: str = "cycle"
debug: str = "debug"
dryrun: str = "dry_run"
file1fmt: str = "file_1_format"
file1path: str = "file_1_path"
Expand Down
6 changes: 3 additions & 3 deletions src/uwtools/config/formats/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import yaml

from uwtools.config.formats.base import Config
from uwtools.config.support import INCLUDE_TAG, TaggedString, add_representers, log_and_error
from uwtools.config.support import INCLUDE_TAG, TaggedString, add_yaml_representers, log_and_error
from uwtools.utils.file import FORMAT, readable, writable

_MSGS = ns(
Expand Down Expand Up @@ -44,7 +44,7 @@ def __repr__(self) -> str:
"""
The string representation of a YAMLConfig object.
"""
add_representers()
add_yaml_representers()
return yaml.dump(self.data, default_flow_style=False).strip()

# Private methods
Expand Down Expand Up @@ -115,7 +115,7 @@ def dump_dict(cfg: dict, path: Optional[Path] = None) -> None:
:param cfg: The in-memory config object to dump.
:param path: Path to dump config to.
"""
add_representers()
add_yaml_representers()
with writable(path) as f:
yaml.dump(cfg, f, sort_keys=False)

Expand Down
15 changes: 7 additions & 8 deletions src/uwtools/config/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@


# Public functions
def add_representers() -> None:


def add_yaml_representers() -> None:
"""
Add representers to the YAML dumper for custom types.
"""
Expand Down Expand Up @@ -66,33 +68,30 @@ def log_and_error(msg: str) -> Exception:


# Private functions


def _represent_namelist(dumper: yaml.Dumper, data: Namelist) -> yaml.nodes.MappingNode:
"""
Convert f90nml Namelist to OrderedDict and serialize.
Convert an f90nml Namelist to an OrderedDict, then represent as a YAML mapping.
:param dumper: The YAML dumper.
:param data: The f90nml Namelist to serialize.
"""
# Convert the f90nml Namelist to an OrderedDict.
namelist_dict = data.todict()

# Represent the OrderedDict as a YAML mapping.
return dumper.represent_mapping("tag:yaml.org,2002:map", namelist_dict)


def _represent_ordereddict(dumper: yaml.Dumper, data: OrderedDict) -> yaml.nodes.MappingNode:
"""
Convert OrderedDict to dict and serialize.
Recursrively convert an OrderedDict to a dict, then represent as a YAML mapping.
:param dumper: The YAML dumper.
:param data: The OrderedDict to serialize.
"""

# Convert the OrderedDict to a dict.
def from_od(d: Union[OrderedDict, Dict]) -> dict:
return {key: from_od(val) if isinstance(val, dict) else val for key, val in d.items()}

# Represent the dict as a YAML mapping.
return dumper.represent_mapping("tag:yaml.org,2002:map", from_od(data))


Expand Down
22 changes: 22 additions & 0 deletions src/uwtools/drivers/support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Dict

import iotaa as _iotaa

from uwtools.drivers.driver import Driver


def graph() -> str:
"""
Returns Graphviz DOT code for the most recently executed task.
"""
return _iotaa.graph()


def tasks(driver_class: type[Driver]) -> Dict[str, str]:
"""
Returns a mapping from task names to their one-line descriptions.
"""
return {
task: getattr(driver_class, task).__doc__.strip().split("\n")[0]
for task in _iotaa.tasknames(driver_class)
}
32 changes: 6 additions & 26 deletions src/uwtools/tests/api/test_fv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import datetime as dt
from unittest.mock import patch

from iotaa import asset, external, task, tasks

from uwtools.api import fv3


Expand All @@ -25,30 +23,12 @@ def test_execute(tmp_path):


def test_graph():
@external
def ready():
yield "ready"
yield asset("ready", lambda: True)

ready()
assert fv3.graph().startswith("digraph")
with patch.object(fv3.support, "graph") as graph:
fv3.graph()
graph.assert_called_once_with()


def test_tasks():
@external
def t1():
"@external t1"

@task
def t2():
"@task t2"

@tasks
def t3():
"@tasks t3"

with patch.object(fv3, "FV3") as FV3:
FV3.t1 = t1
FV3.t2 = t2
FV3.t3 = t3
assert fv3.tasks() == {"t2": "@task t2", "t3": "@tasks t3", "t1": "@external t1"}
with patch.object(fv3.support, "tasks") as _tasks:
fv3.tasks()
_tasks.assert_called_once_with(fv3.FV3)
32 changes: 6 additions & 26 deletions src/uwtools/tests/api/test_sfc_climo_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from unittest.mock import patch

from iotaa import asset, external, task, tasks

from uwtools.api import sfc_climo_gen


Expand All @@ -23,30 +21,12 @@ def test_execute(tmp_path):


def test_graph():
@external
def ready():
yield "ready"
yield asset("ready", lambda: True)

ready()
assert sfc_climo_gen.graph().startswith("digraph")
with patch.object(sfc_climo_gen.support, "graph") as graph:
sfc_climo_gen.graph()
graph.assert_called_once_with()


def test_tasks():
@external
def t1():
"@external t1"

@task
def t2():
"@task t2"

@tasks
def t3():
"@tasks t3"

with patch.object(sfc_climo_gen, "SfcClimoGen") as SfcClimoGen:
SfcClimoGen.t1 = t1
SfcClimoGen.t2 = t2
SfcClimoGen.t3 = t3
assert sfc_climo_gen.tasks() == {"t2": "@task t2", "t3": "@tasks t3", "t1": "@external t1"}
with patch.object(sfc_climo_gen.support, "tasks") as _tasks:
sfc_climo_gen.tasks()
_tasks.assert_called_once_with(sfc_climo_gen.SfcClimoGen)
9 changes: 5 additions & 4 deletions src/uwtools/tests/config/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import logging
from collections import OrderedDict

import f90nml # type: ignore
import pytest
import yaml
from f90nml import Namelist, reads # type: ignore
from f90nml import Namelist
from pytest import fixture, raises

from uwtools.config import support
Expand All @@ -23,8 +24,8 @@
from uwtools.utils.file import FORMAT


def test_add_representers():
support.add_representers()
def test_add_yaml_representers():
support.add_yaml_representers()
representers = yaml.Dumper.yaml_representers
assert support.TaggedString in representers
assert OrderedDict in representers
Expand Down Expand Up @@ -67,7 +68,7 @@ def test_log_and_error(caplog):


def test_represent_namelist():
namelist = reads("&namelist\n key = value\n/\n")
namelist = f90nml.reads("&namelist\n key = value\n/\n")
assert yaml.dump(namelist, default_flow_style=True).strip() == "{namelist: {key: value}}"


Expand Down
Loading

0 comments on commit ce389ed

Please sign in to comment.