Skip to content

Commit

Permalink
Add default output() method to Driver base class (#616)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa authored Oct 22, 2024
1 parent 8f317d6 commit 9963a36
Showing 39 changed files with 284 additions and 46 deletions.
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/chgres_cube/help.out
Original file line number Diff line number Diff line change
@@ -20,5 +20,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/esg_grid/help.out
Original file line number Diff line number Diff line change
@@ -20,5 +20,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/filter_topo/help.out
Original file line number Diff line number Diff line change
@@ -24,5 +24,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/fv3/help.out
Original file line number Diff line number Diff line change
@@ -34,5 +34,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
Original file line number Diff line number Diff line change
@@ -20,5 +20,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/ioda/help.out
Original file line number Diff line number Diff line change
@@ -24,5 +24,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/jedi/help.out
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
validate_only
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/make_hgrid/help.out
Original file line number Diff line number Diff line change
@@ -18,5 +18,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
Original file line number Diff line number Diff line change
@@ -18,5 +18,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/mpas/help.out
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
streams_file
The streams file
validate
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/mpas_init/help.out
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
streams_file
The streams file
validate
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/orog/help.out
Original file line number Diff line number Diff line change
@@ -24,5 +24,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/orog_gsl/help.out
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
topo_data_2p5m
Global topographic data on 2.5-minute lat-lon grid
topo_data_30s
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/sfc_climo_gen/help.out
Original file line number Diff line number Diff line change
@@ -20,5 +20,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/shave/help.out
Original file line number Diff line number Diff line change
@@ -20,5 +20,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/ungrib/help.out
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
vtable
2 changes: 2 additions & 0 deletions docs/sections/user_guide/cli/drivers/upp/help.out
Original file line number Diff line number Diff line change
@@ -24,5 +24,7 @@ Positional arguments:
A run
runscript
The runscript
show_output
Show the output to be created by this component
validate
Validate the UW driver config
22 changes: 20 additions & 2 deletions src/uwtools/drivers/driver.py
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@
validate_external,
validate_internal,
)
from uwtools.exceptions import UWConfigError
from uwtools.exceptions import UWConfigError, UWNotImplementedError
from uwtools.logging import log
from uwtools.scheduler import JobScheduler
from uwtools.strings import STR
@@ -393,6 +393,15 @@ def runscript(self):
yield None
self._write_runscript(path)

@external
def show_output(self):
"""
Show the output to be created by this component.
"""
yield self.taskname("expected output")
print(json.dumps(self.output, indent=2, sort_keys=True))
yield asset(None, lambda: True)

@task
def _run_via_batch_submission(self):
"""
@@ -416,7 +425,16 @@ def _run_via_local_execution(self):
cmd = "{x} >{x}.out 2>&1".format(x=self._runscript_path)
run_shell_cmd(cmd=cmd, cwd=self.rundir, log_output=True)

# Private helper methods
# Public methods

@property
def output(self) -> dict[str, Union[str, list[str]]]:
"""
Returns a description of the file(s) created when this component runs.
"""
raise UWNotImplementedError("The output() method is not yet implemented for this driver")

# Private methods

@property
def _run_resources(self) -> dict[str, Any]:
6 changes: 6 additions & 0 deletions src/uwtools/exceptions.py
Original file line number Diff line number Diff line change
@@ -21,6 +21,12 @@ class UWConfigRealizeError(UWConfigError):
"""


class UWNotImplementedError(UWError):
"""
Exception for signaling that uwtools has not (yet) implemented something.
"""


class UWTemplateRenderError(UWError):
"""
Exception for issues arising from template rendering.
9 changes: 9 additions & 0 deletions src/uwtools/tests/drivers/test_cdeps.py
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
from uwtools.config.formats.nml import NMLConfig
from uwtools.drivers import cdeps
from uwtools.drivers.cdeps import CDEPS
from uwtools.drivers.driver import AssetsCycleBased
from uwtools.logging import log
from uwtools.tests.support import logged
from uwtools.tests.test_schemas import CDEPS_CONFIG
@@ -35,6 +36,14 @@ def driverobj(tmp_path):
# Tests


@mark.parametrize(
"method",
["taskname", "_validate"],
)
def test_CDEPS(method):
assert getattr(CDEPS, method) is getattr(AssetsCycleBased, method)


def test_CDEPS_atm(driverobj):
with patch.object(CDEPS, "atm_nml") as atm_nml:
with patch.object(CDEPS, "atm_stream") as atm_stream:
10 changes: 9 additions & 1 deletion src/uwtools/tests/drivers/test_chgres_cube.py
Original file line number Diff line number Diff line change
@@ -10,10 +10,11 @@

import f90nml # type: ignore
from iotaa import refs
from pytest import fixture, mark
from pytest import fixture, mark, raises

from uwtools.drivers.chgres_cube import ChgresCube
from uwtools.drivers.driver import Driver
from uwtools.exceptions import UWNotImplementedError
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import logged, regex_logged
@@ -101,6 +102,7 @@ def driverobj(config, cycle):
"_scheduler",
"_validate",
"_write_runscript",
"output",
"run",
],
)
@@ -140,6 +142,12 @@ def test_ChgresCube_namelist_file_missing_base_file(caplog, driverobj):
assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)")


def test_ChgresCube_output(driverobj):
with raises(UWNotImplementedError) as e:
assert driverobj.output
assert str(e.value) == "The output() method is not yet implemented for this driver"


def test_ChgresCube_provisioned_rundir(driverobj):
with patch.multiple(driverobj, namelist_file=D, runscript=D) as mocks:
driverobj.provisioned_rundir()
88 changes: 61 additions & 27 deletions src/uwtools/tests/drivers/test_driver.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@

from uwtools.config.formats.yaml import YAMLConfig
from uwtools.drivers import driver
from uwtools.exceptions import UWConfigError
from uwtools.exceptions import UWConfigError, UWNotImplementedError
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import regex_logged
@@ -68,7 +68,11 @@ class ConcreteDriverCycleLeadtimeBased(Common, driver.DriverCycleLeadtimeBased):


class ConcreteDriverTimeInvariant(Common, driver.DriverTimeInvariant):
pass

@property
def output(self):
# The dict keys are intentionally out-of-alphabetical-order to test JSON sorting.
return {"bar": ["/path/to/bar1", "/path/to/bar2"], "foo": "/path/to/foo"}


def write(path, x):
@@ -397,6 +401,22 @@ def test_Driver_namelist_schema_default_disable(driverobj):
assert driverobj.namelist_schema() == {"type": "object"}


def test_Driver_output(config):
driverobj = ConcreteDriverTimeInvariant(config)
assert driverobj.output == {"foo": "/path/to/foo", "bar": ["/path/to/bar1", "/path/to/bar2"]}


@mark.parametrize("cls", [ConcreteDriverCycleBased, ConcreteDriverCycleLeadtimeBased])
def test_Driver_output_not_implemented(cls, config):
kwargs = {"config": config, "cycle": dt.datetime(2024, 10, 22, 12)}
if cls == ConcreteDriverCycleLeadtimeBased:
kwargs["leadtime"] = 6
driverobj = cls(**kwargs)
with raises(UWNotImplementedError) as e:
assert driverobj.output
assert str(e.value) == "The output() method is not yet implemented for this driver"


@mark.parametrize("batch", [True, False])
def test_Driver_run(batch, driverobj):
driverobj._batch = batch
@@ -423,6 +443,45 @@ def test_Driver_runscript(arg, driverobj, type_):
assert isinstance(runscript.call_args.kwargs[arg], type_)


def test_driver_show_output(capsys, config):
ConcreteDriverTimeInvariant(config).show_output()
expected = """
{
"bar": [
"/path/to/bar1",
"/path/to/bar2"
],
"foo": "/path/to/foo"
}
"""
assert capsys.readouterr().out.strip() == dedent(expected).strip()


@mark.parametrize(
"base_file,update_values,expected",
[
(False, False, {}),
(False, True, {"a": 33}),
(True, False, {"a": 11, "b": 22}),
(True, True, {"a": 33, "b": 22}),
],
)
def test_Driver__create_user_updated_config_base_file(
base_file, driverobj, expected, tmp_path, update_values
):
path = tmp_path / "updated.yaml"
if not base_file:
del driverobj._config["base_file"]
if not update_values:
del driverobj._config["update_values"]
ConcreteDriverTimeInvariant._create_user_updated_config(
config_class=YAMLConfig, config_values=driverobj.config, path=path
)
with open(path, "r", encoding="utf-8") as f:
updated = yaml.safe_load(f)
assert updated == expected


def test_Driver__run_via_batch_submission(driverobj):
runscript = driverobj._runscript_path
executable = Path(driverobj.config["execution"]["executable"])
@@ -452,31 +511,6 @@ def test_Driver__run_via_local_execution(driverobj):
prd.assert_called_once_with()


@mark.parametrize(
"base_file,update_values,expected",
[
(False, False, {}),
(False, True, {"a": 33}),
(True, False, {"a": 11, "b": 22}),
(True, True, {"a": 33, "b": 22}),
],
)
def test_Driver__create_user_updated_config_base_file(
base_file, driverobj, expected, tmp_path, update_values
):
path = tmp_path / "updated.yaml"
if not base_file:
del driverobj._config["base_file"]
if not update_values:
del driverobj._config["update_values"]
ConcreteDriverTimeInvariant._create_user_updated_config(
config_class=YAMLConfig, config_values=driverobj.config, path=path
)
with open(path, "r", encoding="utf-8") as f:
updated = yaml.safe_load(f)
assert updated == expected


def test_Driver__run_resources_fail(driverobj):
del driverobj._config_intermediate["platform"]
with raises(UWConfigError) as e:
Loading

0 comments on commit 9963a36

Please sign in to comment.