diff --git a/notebooks/tests/test_rocoto.py b/notebooks/tests/test_rocoto.py index 389b8851d..ae1499c0c 100644 --- a/notebooks/tests/test_rocoto.py +++ b/notebooks/tests/test_rocoto.py @@ -20,9 +20,9 @@ def test_building_simple_workflow(): assert tb.cell_output_text(11) == err_yaml err_out = ( "ERROR 3 UW schema-validation errors found", - "ERROR Error at workflow -> attrs:", + "ERROR Error at workflow.attrs:", "ERROR 'realtime' is a required property", - "ERROR Error at workflow -> tasks -> task_greet:", + "ERROR Error at workflow.tasks.task_greet:", "ERROR 'command' is a required property", "ERROR Error at workflow:", "ERROR 'log' is a required property", 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 2a0acd0e4..ddf408963 100644 --- a/src/uwtools/drivers/chgres_cube.py +++ b/src/uwtools/drivers/chgres_cube.py @@ -25,6 +25,15 @@ def namelist_file(self): """ The namelist file. """ + + def update_input_files(k, config_files, input_files): + v = config_files[k] + context = ".".join(["config", k]) + if isinstance(v, str): + input_files.append((grid_path / v, context)) + else: + input_files.extend([(grid_path / f, context) for f in v]) + fn = "fort.41" yield self.taskname(f"namelist file {fn}") path = self.rundir / fn @@ -32,11 +41,13 @@ def namelist_file(self): input_files = [] namelist = self.config[STR.namelist] if base_file := namelist.get(STR.basefile): - input_files.append(base_file) + context = ".".join([STR.namelist, STR.basefile]) + input_files.append((base_file, context)) 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"]: - input_files.append(config_files[k]) + context = ".".join(["config", k]) + input_files.append((config_files[k], context)) for k in [ "atm_core_files_input_grid", "atm_files_input_grid", @@ -47,23 +58,15 @@ def namelist_file(self): ]: if k in config_files: grid_path = Path(config_files["data_dir_input_grid"]) - v = config_files[k] - if isinstance(v, str): - input_files.append(grid_path / v) - else: - input_files.extend([grid_path / f for f in v]) + update_input_files(k, config_files, input_files) for k in [ "orog_files_input_grid", "orog_files_target_grid", ]: if k in config_files: grid_path = Path(config_files[k.replace("files", "dir")]) - v = config_files[k] - if isinstance(v, str): - input_files.append(grid_path / v) - else: - input_files.extend([grid_path / f for f in v]) - yield [file(Path(input_file)) for input_file in input_files] + update_input_files(k, config_files, input_files) + yield [file(Path(input_file), context) for input_file, context 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 f8494a28e..d8385c47f 100644 --- a/src/uwtools/tests/drivers/test_chgres_cube.py +++ b/src/uwtools/tests/drivers/test_chgres_cube.py @@ -144,7 +144,9 @@ 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)") + assert regex_logged( + caplog, "missing.nml (namelist.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 eef1c9deb..7659fe2be 100644 --- a/src/uwtools/utils/tasks.py +++ b/src/uwtools/utils/tasks.py @@ -46,13 +46,15 @@ def existing(path: Path): @external -def file(path: Path): +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. """ - yield "File %s" % path + suffix = f" ({context})" if context else "" + yield "File %s%s" % (path, suffix) yield asset(path, path.is_file)