From c6a6d56a32b862a927b10466dfd76c668cecdb2d Mon Sep 17 00:00:00 2001 From: Travis Byrne Date: Tue, 26 Nov 2024 10:12:59 -0700 Subject: [PATCH] Improved chgres_cube logging & dot delimited key paths --- src/uwtools/config/tools.py | 2 +- src/uwtools/config/validator.py | 2 +- src/uwtools/drivers/chgres_cube.py | 25 ++++++------------- src/uwtools/fs.py | 2 +- src/uwtools/tests/config/test_tools.py | 6 ++--- src/uwtools/tests/drivers/test_chgres_cube.py | 12 +-------- src/uwtools/tests/test_fs.py | 2 +- src/uwtools/utils/tasks.py | 7 +++--- 8 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/uwtools/config/tools.py b/src/uwtools/config/tools.py index e92e84c6d..305e4808a 100644 --- a/src/uwtools/config/tools.py +++ b/src/uwtools/config/tools.py @@ -121,7 +121,7 @@ def walk_key_path(config: dict, key_path: list[str]) -> tuple[dict, str]: pathstr = "" for key in key_path: keys.append(key) - pathstr = " -> ".join(keys) + pathstr = ".".join(keys) try: subconfig = config[key] except KeyError as e: diff --git a/src/uwtools/config/validator.py b/src/uwtools/config/validator.py index 8eccec18a..5e86f0aec 100644 --- a/src/uwtools/config/validator.py +++ b/src/uwtools/config/validator.py @@ -71,7 +71,7 @@ def validate(schema: dict, desc: str, config: dict) -> bool: log_msg = "%s UW schema-validation error%s found in %s" log_method(log_msg, len(errors), "" if len(errors) == 1 else "s", desc) for error in errors: - log.error("Error at %s:", " -> ".join(str(k) for k in error.path)) + log.error("Error at %s:", ".".join(str(k) for k in error.path)) log.error("%s%s", INDENT, error.message) return not bool(errors) diff --git a/src/uwtools/drivers/chgres_cube.py b/src/uwtools/drivers/chgres_cube.py index 824ab3c22..76e0f66e7 100644 --- a/src/uwtools/drivers/chgres_cube.py +++ b/src/uwtools/drivers/chgres_cube.py @@ -21,7 +21,7 @@ class ChgresCube(DriverCycleLeadtimeBased): # Workflow tasks @task - def namelist_file(self, log_path: bool = False): + def namelist_file(self): """ The namelist file. """ @@ -30,18 +30,13 @@ def namelist_file(self, log_path: bool = False): path = self.rundir / fn yield asset(path, path.is_file) input_files = [] - pathstr = "" namelist = self.config[STR.namelist] if base_file := namelist.get(STR.basefile): - if log_path: - pathstr = " -> ".join([STR.namelist, STR.basefile]) - input_files.append((pathstr, base_file)) + input_files.append((base_file, STR.basefile)) if update_values := namelist.get(STR.updatevalues): config_files = update_values["config"] for k in ["mosaic_file_target_grid", "varmap_file", "vcoord_file_target_grid"]: - if log_path: - pathstr = " -> ".join([STR.namelist, STR.updatevalues, "config", k]) - input_files.append((pathstr, config_files[k])) + input_files.append((config_files[k], k)) for k in [ "atm_core_files_input_grid", "atm_files_input_grid", @@ -53,12 +48,10 @@ def namelist_file(self, log_path: bool = False): if k in config_files: grid_path = Path(config_files["data_dir_input_grid"]) v = config_files[k] - if log_path: - pathstr = " -> ".join([STR.namelist, STR.updatevalues, "config", k]) if isinstance(v, str): - input_files.append((pathstr, grid_path / v)) + input_files.append((grid_path / v, k)) else: - input_files.extend([(pathstr, grid_path / f) for f in v]) + input_files.extend([(grid_path / f, k) for f in v]) for k in [ "orog_files_input_grid", "orog_files_target_grid", @@ -66,13 +59,11 @@ def namelist_file(self, log_path: bool = False): if k in config_files: grid_path = Path(config_files[k.replace("files", "dir")]) v = config_files[k] - if log_path: - pathstr = " -> ".join([STR.namelist, STR.updatevalues, "config", k]) if isinstance(v, str): - input_files.append((pathstr, grid_path / v)) + input_files.append((grid_path / v, k)) else: - input_files.extend([(pathstr, grid_path / f) for f in v]) - yield [file(Path(input_file), pathstr) for pathstr, input_file in input_files] + input_files.extend([(grid_path / f, k) for f in v]) + yield [file(Path(input_file), pathstr) for input_file, pathstr in input_files] self._create_user_updated_config( config_class=NMLConfig, config_values=namelist, diff --git a/src/uwtools/fs.py b/src/uwtools/fs.py index 9f9d539c4..46bf68244 100644 --- a/src/uwtools/fs.py +++ b/src/uwtools/fs.py @@ -83,7 +83,7 @@ def _set_config_block(self) -> None: for key in self._keys: nav.append(key) if key not in cfg: - raise UWConfigError("Failed following YAML key(s): %s" % " -> ".join(nav)) + raise UWConfigError("Failed following YAML key(s): %s" % ".".join(nav)) log.debug("Following config key '%s'", key) cfg = cfg[key] if not isinstance(cfg, dict): diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index 69e5c77e7..f7103628a 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -556,17 +556,17 @@ def test_realize_config_values_needed_yaml(caplog): def test_walk_key_path_fail_bad_key_path(): with raises(UWError) as e: tools.walk_key_path({"a": {"b": {"c": "cherry"}}}, ["a", "x"]) - assert str(e.value) == "Bad config path: a -> x" + assert str(e.value) == "Bad config path: a.x" def test_walk_key_path_fail_bad_leaf_value(): with raises(UWError) as e: tools.walk_key_path({"a": {"b": {"c": "cherry"}}}, ["a", "b", "c"]) - assert str(e.value) == "Value at a -> b -> c must be a dictionary" + assert str(e.value) == "Value at a.b.c must be a dictionary" def test_walk_key_path_pass(): - expected = ({"c": "cherry"}, "a -> b") + expected = ({"c": "cherry"}, "a.b") assert tools.walk_key_path({"a": {"b": {"c": "cherry"}}}, ["a", "b"]) == expected diff --git a/src/uwtools/tests/drivers/test_chgres_cube.py b/src/uwtools/tests/drivers/test_chgres_cube.py index 9ff0fc9ac..8a5f127b5 100644 --- a/src/uwtools/tests/drivers/test_chgres_cube.py +++ b/src/uwtools/tests/drivers/test_chgres_cube.py @@ -144,17 +144,7 @@ def test_ChgresCube_namelist_file_missing_base_file(caplog, driverobj): driverobj._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_namelist_file_missing_base_file_path_logging(caplog, driverobj): - log.setLevel(logging.DEBUG) - base_file = str(Path(driverobj.config["rundir"], "missing.nml")) - driverobj._config["namelist"]["base_file"] = base_file - path = Path(refs(driverobj.namelist_file(log_path=True))) - assert not path.exists() - assert regex_logged(caplog, "namelist -> base_file: ") - assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)") + assert regex_logged(caplog, "missing.nml (base_file): State: Not Ready (external asset)") def test_ChgresCube_output(driverobj): diff --git a/src/uwtools/tests/test_fs.py b/src/uwtools/tests/test_fs.py index b313267bd..6e54dd07d 100644 --- a/src/uwtools/tests/test_fs.py +++ b/src/uwtools/tests/test_fs.py @@ -109,7 +109,7 @@ def test_Stager__config_block_fail_bad_key_path(assets, source): config = cfgdict if source == "dict" else cfgfile with raises(UWConfigError) as e: ConcreteStager(target_dir=dstdir, config=config, keys=["a", "x"]) - assert str(e.value) == "Failed following YAML key(s): a -> x" + assert str(e.value) == "Failed following YAML key(s): a.x" @mark.parametrize("val", [None, True, False, "str", 42, 3.14, [], tuple()]) diff --git a/src/uwtools/utils/tasks.py b/src/uwtools/utils/tasks.py index d53a5e1b2..35ccf7d7e 100644 --- a/src/uwtools/utils/tasks.py +++ b/src/uwtools/utils/tasks.py @@ -46,14 +46,15 @@ def existing(path: Path): @external -def file(path: Path, pathstr: str = ""): +def file(path: Path, context: str = ""): """ An existing file or symlink to an existing file. :param path: Path to the file. + :param context: Optional additional context for the file. """ - prefix = pathstr + ": " if pathstr else "" - yield "%sFile %s" % (prefix, path) + suffix = " (" + context + ")" if context else "" + yield "File %s%s" % (path, suffix) yield asset(path, path.is_file)