Skip to content

Commit

Permalink
UW-589 Yield a requirement on base_file when used (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa authored Jun 11, 2024
1 parent f4f1ac1 commit 3509e23
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 85 deletions.
5 changes: 4 additions & 1 deletion src/uwtools/drivers/chgres_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ def namelist_file(self):
Path(config_files["data_dir_input_grid"]) / config_files[k]
for k in ("atm_files_input_grid", "grib2_file_input_grid", "sfc_files_input_grid")
]
yield [file(input_path) for input_path in input_paths]
base_file = self._driver_config["namelist"].get("base_file")
yield [file(input_path) for input_path in input_paths] + (
[file(Path(base_file))] if base_file else []
)
self._create_user_updated_config(
config_class=NMLConfig,
config_values=self._driver_config["namelist"],
Expand Down
4 changes: 3 additions & 1 deletion src/uwtools/drivers/esg_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from uwtools.config.formats.nml import NMLConfig
from uwtools.drivers.driver import Driver
from uwtools.strings import STR
from uwtools.utils.tasks import file


class ESGGrid(Driver):
Expand Down Expand Up @@ -45,7 +46,8 @@ def namelist_file(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["namelist"].get("base_file")
yield file(Path(base_file)) if base_file else None
self._create_user_updated_config(
config_class=NMLConfig,
config_values=self._driver_config["namelist"],
Expand Down
8 changes: 5 additions & 3 deletions src/uwtools/drivers/fv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from uwtools.drivers.driver import Driver
from uwtools.logging import log
from uwtools.strings import STR
from uwtools.utils.tasks import filecopy, symlink
from uwtools.utils.tasks import file, filecopy, symlink


class FV3(Driver):
Expand Down Expand Up @@ -124,7 +124,8 @@ def model_configure(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["model_configure"].get("base_file")
yield file(Path(base_file)) if base_file else None
self._create_user_updated_config(
config_class=YAMLConfig,
config_values=self._driver_config["model_configure"],
Expand All @@ -140,7 +141,8 @@ def namelist_file(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["namelist"].get("base_file")
yield file(Path(base_file)) if base_file else None
self._create_user_updated_config(
config_class=NMLConfig,
config_values=self._driver_config["namelist"],
Expand Down
3 changes: 2 additions & 1 deletion src/uwtools/drivers/jedi.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def configuration_file(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["configuration_file"].get("base_file")
yield file(Path(base_file)) if base_file else None
self._create_user_updated_config(
config_class=YAMLConfig,
config_values=self._driver_config["configuration_file"],
Expand Down
13 changes: 4 additions & 9 deletions src/uwtools/drivers/mpas.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@

from uwtools.config.formats.nml import NMLConfig
from uwtools.drivers.mpas_base import MPASBase
from uwtools.exceptions import UWConfigError
from uwtools.strings import STR
from uwtools.utils.tasks import symlink
from uwtools.utils.tasks import file, symlink


class MPAS(MPASBase):
Expand Down Expand Up @@ -46,15 +45,11 @@ def namelist_file(self):
path = self._rundir / "namelist.atmosphere"
yield self._taskname(str(path))
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["namelist"].get("base_file")
yield file(Path(base_file)) if base_file else None
duration = timedelta(hours=self._driver_config["length"])
str_duration = str(duration).replace(" days, ", "")
try:
namelist = self._driver_config["namelist"]
except KeyError as e:
raise UWConfigError(
"Provide either a 'namelist' YAML block or the %s file" % path
) from e
namelist = self._driver_config["namelist"]
update_values = namelist.get("update_values", {})
update_values.setdefault("nhyd_model", {}).update(
{
Expand Down
13 changes: 4 additions & 9 deletions src/uwtools/drivers/mpas_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@

from uwtools.config.formats.nml import NMLConfig
from uwtools.drivers.mpas_base import MPASBase
from uwtools.exceptions import UWConfigError
from uwtools.strings import STR
from uwtools.utils.tasks import symlink
from uwtools.utils.tasks import file, symlink


class MPASInit(MPASBase):
Expand Down Expand Up @@ -49,16 +48,12 @@ def namelist_file(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["namelist"].get("base_file")
yield file(Path(base_file)) if base_file else None
stop_time = self._cycle + timedelta(
hours=self._driver_config["boundary_conditions"]["length"]
)
try:
namelist = self._driver_config["namelist"]
except KeyError as e:
raise UWConfigError(
"Provide either a 'namelist' YAML block or the %s file" % path
) from e
namelist = self._driver_config["namelist"]
update_values = namelist.get("update_values", {})
update_values.setdefault("nhyd_model", {}).update(
{
Expand Down
5 changes: 3 additions & 2 deletions src/uwtools/drivers/upp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from uwtools.config.formats.nml import NMLConfig
from uwtools.drivers.driver import Driver
from uwtools.strings import STR
from uwtools.utils.tasks import filecopy, symlink
from uwtools.utils.tasks import file, filecopy, symlink


class UPP(Driver):
Expand Down Expand Up @@ -81,7 +81,8 @@ def namelist_file(self):
path = self._namelist_path
yield self._taskname(str(path))
yield asset(path, path.is_file)
yield None
base_file = self._driver_config["namelist"].get("base_file")
yield file(Path(base_file)) if base_file else None
path.parent.mkdir(parents=True, exist_ok=True)
self._create_user_updated_config(
config_class=NMLConfig,
Expand Down
5 changes: 3 additions & 2 deletions src/uwtools/drivers/ww3.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ def namelist_file(self):
yield self._taskname(fn)
path = self._rundir / fn
yield asset(path, path.is_file)
yield file(path=Path(self._driver_config["namelist"]["template_file"]))
template_file = Path(self._driver_config["namelist"]["template_file"])
yield file(template_file)
render(
input_file=Path(self._driver_config["namelist"]["template_file"]),
input_file=template_file,
output_file=path,
overrides=self._driver_config["namelist"]["template_values"],
)
Expand Down
57 changes: 28 additions & 29 deletions src/uwtools/tests/drivers/test_chgres_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

import f90nml # type: ignore
import yaml
from iotaa import asset, external, refs
from iotaa import refs
from pytest import fixture

from uwtools.drivers import chgres_cube
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import logged
from uwtools.tests.support import logged, regex_logged

# Fixtures

Expand All @@ -28,6 +28,8 @@ def cycle():

@fixture
def config_file(tmp_path):
afile = tmp_path / "afile"
afile.touch()
config: dict = {
"chgres_cube": {
"execution": {
Expand All @@ -45,34 +47,33 @@ def config_file(tmp_path):
"namelist": {
"update_values": {
"config": {
"atm_core_files_input_grid": "/path/to/file",
"atm_files_input_grid": "/path/to/file",
"atm_tracer_files_input_grid": "/path/to/file",
"atm_weight_file": "/path/to/file",
"atm_core_files_input_grid": str(afile),
"atm_files_input_grid": str(afile),
"atm_tracer_files_input_grid": str(afile),
"atm_weight_file": str(afile),
"convert_atm": True,
"data_dir_input_grid": "/path/to/file",
"data_dir_input_grid": str(afile),
"external_model": "GFS",
"fix_dir_target_grid": "/path/to/dir",
"geogrid_file_input_grid": "/path/to/file",
"grib2_file_input_grid": "/path/to/file",
"mosaic_file_input_grid": "/path/to/file",
"mosaic_file_target_grid": "/path/to/file",
"sfc_files_input_grid": "/path/to/file",
"varmap_file": "/path/to/file",
"vcoord_file_target_grid": "/path/to/file",
"geogrid_file_input_grid": str(afile),
"grib2_file_input_grid": str(afile),
"mosaic_file_input_grid": str(afile),
"mosaic_file_target_grid": str(afile),
"sfc_files_input_grid": str(afile),
"varmap_file": str(afile),
"vcoord_file_target_grid": str(afile),
}
},
"validate": True,
},
"run_dir": "/path/to/dir",
"run_dir": str(tmp_path),
},
"platform": {
"account": "me",
"scheduler": "slurm",
},
}
path = tmp_path / "config.yaml"
config["chgres_cube"]["run_dir"] = tmp_path.as_posix()
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f)
return path
Expand All @@ -83,15 +84,6 @@ def driverobj(config_file, cycle):
return chgres_cube.ChgresCube(config=config_file, cycle=cycle, batch=True)


# Helpers


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


# Tests


Expand All @@ -103,8 +95,7 @@ def test_ChgresCube_namelist_file(caplog, driverobj):
log.setLevel(logging.DEBUG)
dst = driverobj._rundir / "fort.41"
assert not dst.is_file()
with patch.object(chgres_cube, "file", new=ready):
path = Path(refs(driverobj.namelist_file()))
path = Path(refs(driverobj.namelist_file()))
assert dst.is_file()
assert logged(caplog, f"Wrote config to {path}")
assert isinstance(f90nml.read(dst), f90nml.Namelist)
Expand All @@ -113,13 +104,21 @@ def test_ChgresCube_namelist_file(caplog, driverobj):
def test_ChgresCube_namelist_file_fails_validation(caplog, driverobj):
log.setLevel(logging.DEBUG)
driverobj._driver_config["namelist"]["update_values"]["config"]["convert_atm"] = "string"
with patch.object(chgres_cube, "file", new=ready):
path = Path(refs(driverobj.namelist_file()))
path = Path(refs(driverobj.namelist_file()))
assert not path.exists()
assert logged(caplog, f"Failed to validate {path}")
assert logged(caplog, " 'string' is not of type 'boolean'")


def test_ChgresCube_namelist_file_missing_base_file(caplog, driverobj):
log.setLevel(logging.DEBUG)
base_file = str(Path(driverobj._driver_config["run_dir"]) / "missing.nml")
driverobj._driver_config["namelist"]["base_file"] = base_file
path = Path(refs(driverobj.namelist_file()))
assert not path.exists()
assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)")


def test_ChgresCube_provisioned_run_directory(driverobj):
with patch.multiple(driverobj, namelist_file=D, runscript=D) as mocks:
driverobj.provisioned_run_directory()
Expand Down
11 changes: 10 additions & 1 deletion src/uwtools/tests/drivers/test_esg_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from uwtools.drivers import esg_grid
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import logged
from uwtools.tests.support import logged, regex_logged

# Fixtures

Expand Down Expand Up @@ -94,6 +94,15 @@ def test_ESGGrid_namelist_file_fails_validation(caplog, driverobj):
assert logged(caplog, " 'string' is not of type 'number'")


def test_ESGGrid_namelist_file_missing_base_file(caplog, driverobj):
log.setLevel(logging.DEBUG)
base_file = str(Path(driverobj._driver_config["run_dir"]) / "missing.nml")
driverobj._driver_config["namelist"]["base_file"] = base_file
path = Path(refs(driverobj.namelist_file()))
assert not path.exists()
assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)")


def test_ESGGrid_provisioned_run_directory(driverobj):
with patch.multiple(
driverobj,
Expand Down
26 changes: 21 additions & 5 deletions src/uwtools/tests/drivers/test_fv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from uwtools.drivers import driver, fv3
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import logged
from uwtools.tests.support import logged, regex_logged

# Fixtures

Expand Down Expand Up @@ -150,15 +150,22 @@ def test_FV3_files_copied_and_linked(config, cycle, key, task, test, tmp_path):
assert all(getattr(dst, test)() for dst in [atm_dst, sfc_dst])


def test_FV3_model_configure(driverobj):
@pytest.mark.parametrize("base_file_exists", [True, False])
def test_FV3_model_configure(base_file_exists, caplog, driverobj):
log.setLevel(logging.DEBUG)
src = driverobj._rundir / "model_configure.in"
with open(src, "w", encoding="utf-8") as f:
yaml.dump({}, f)
if base_file_exists:
with open(src, "w", encoding="utf-8") as f:
yaml.dump({}, f)
dst = driverobj._rundir / "model_configure"
assert not dst.is_file()
driverobj._driver_config["model_configure"] = {"base_file": src}
driverobj.model_configure()
assert dst.is_file()
if base_file_exists:
assert dst.is_file()
else:
assert not dst.is_file()
assert regex_logged(caplog, f"{src}: State: Not Ready (external asset)")


def test_FV3_namelist_file(caplog, driverobj):
Expand All @@ -183,6 +190,15 @@ def test_FV3_namelist_file_fails_validation(caplog, driverobj):
assert logged(caplog, " None is not of type 'array', 'boolean', 'number', 'string'")


def test_FV3_namelist_file_missing_base_file(caplog, driverobj):
log.setLevel(logging.DEBUG)
base_file = str(Path(driverobj._driver_config["run_dir"]) / "missing.nml")
driverobj._driver_config["namelist"]["base_file"] = base_file
path = Path(refs(driverobj.namelist_file()))
assert not path.exists()
assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)")


@pytest.mark.parametrize("domain", ("global", "regional"))
def test_FV3_provisioned_run_directory(domain, driverobj):
driverobj._driver_config["domain"] = domain
Expand Down
16 changes: 14 additions & 2 deletions src/uwtools/tests/drivers/test_jedi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from uwtools.config.formats.yaml import YAMLConfig
from uwtools.drivers import jedi
from uwtools.logging import log
from uwtools.scheduler import Slurm
from uwtools.tests.support import regex_logged

Expand Down Expand Up @@ -176,8 +177,8 @@ def file(path: Path):

def test_JEDI_configuration_file(driverobj):
basecfg = {"foo": "bar"}
basefile = Path(driverobj._driver_config["configuration_file"]["base_file"])
with open(basefile, "w", encoding="utf-8") as f:
base_file = Path(driverobj._driver_config["configuration_file"]["base_file"])
with open(base_file, "w", encoding="utf-8") as f:
yaml.dump(basecfg, f)
cfgfile = Path(driverobj._driver_config["run_dir"]) / "jedi.yaml"
assert not cfgfile.is_file()
Expand All @@ -187,6 +188,17 @@ def test_JEDI_configuration_file(driverobj):
assert newcfg == {**basecfg, "baz": "qux"}


def test_JEDI_configuration_file_missing_base_file(caplog, driverobj):
log.setLevel(logging.DEBUG)
base_file = Path(driverobj._driver_config["run_dir"]) / "missing"
driverobj._driver_config["configuration_file"]["base_file"] = base_file
cfgfile = Path(driverobj._driver_config["run_dir"]) / "jedi.yaml"
assert not cfgfile.is_file()
driverobj.configuration_file()
assert not cfgfile.is_file()
assert regex_logged(caplog, f"{base_file}: State: Not Ready (external asset)")


def test_JEDI__config_fn(driverobj):
assert driverobj._config_fn == "jedi.yaml"

Expand Down
Loading

0 comments on commit 3509e23

Please sign in to comment.