Skip to content

Commit

Permalink
UW-269 Extend set_config.py tool to "export" variables (#280)
Browse files Browse the repository at this point in the history
* added export_variables function

* updated section_path type

* config_dict -> config

* updated doc string

* ordered type values

* updated function name

* added dict type checking

* added list handling

* added helper functions

* adding Paul's reorg

* re-order functions

* decluttering

* convert to sorted set

* output_lines as a list

* removed list functionality

* removed superfluous test

* fixed list handling error

* reorg

* disable protected access

* name change
  • Loading branch information
elcarpenterNOAA authored Aug 18, 2023
1 parent 8a4f1d1 commit c2705b4
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
39 changes: 39 additions & 0 deletions src/uwtools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import yaml

from uwtools import exceptions, logger
from uwtools.exceptions import UWConfigError
from uwtools.j2template import J2Template
from uwtools.logger import Logger
from uwtools.utils import cli_helpers
Expand Down Expand Up @@ -653,6 +654,20 @@ def dump_file_from_dict(path: str, cfg: dict, opts: Optional[ns] = None) -> None
file_name.write("\n".join(lines))


# Private functions


def _log_and_error(msg: str, log: Logger) -> None:
"""
Will log a user-provided error message and raise a UWConfigError with the same message.
"""
log.error(msg)
raise UWConfigError(msg)


# Public functions


def create_config_obj(
input_base_file: str,
compare: bool = False,
Expand Down Expand Up @@ -752,3 +767,27 @@ def create_config_obj(
raise ValueError(err_msg)
# Dump to file:
dump_method(path=outfile, cfg=config_obj)


def print_config_section(config: dict, key_path: List[str], log: Logger) -> None:
"""
Descends into the config via the given keys, then prints the contents of the located subtree as
key=value pairs, one per line.
"""
keys = []
for section in key_path:
keys.append(section)
current_path = " -> ".join(keys)
try:
subconfig = config[section]
except KeyError:
_log_and_error(f"Bad config path: {current_path}", log)
if not isinstance(subconfig, dict):
_log_and_error(f"Value at {current_path} must be a dictionary", log)
config = subconfig
output_lines = []
for key, value in config.items():
if type(value) not in (bool, float, int, str):
_log_and_error(f"Non-scalar value {value} found at {current_path}", log)
output_lines.append(f"{key}={value}")
print("\n".join(sorted(output_lines)))
68 changes: 67 additions & 1 deletion src/uwtools/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=duplicate-code,missing-function-docstring,redefined-outer-name
# pylint: disable=duplicate-code,missing-function-docstring,protected-access,redefined-outer-name
"""
Tests for uwtools.config module.
"""
Expand All @@ -22,6 +22,7 @@

from uwtools import config, exceptions
from uwtools.exceptions import UWConfigError
from uwtools.logger import Logger
from uwtools.tests.support import compare_files, fixture_path, line_in_lines, msg_in_caplog
from uwtools.utils import cli_helpers

Expand Down Expand Up @@ -810,3 +811,68 @@ def test_YAMLConfig__load_unexpected_error(tmp_path):
with raises(UWConfigError) as e:
config.YAMLConfig(config_path=cfgfile)
assert msg in str(e.value)


def test_print_config_section_ini(capsys):
config_obj = config.INIConfig(fixture_path("simple3.ini"))
section = ["dessert"]
config.print_config_section(config_obj.data, section, log=Logger())
actual = capsys.readouterr().out
expected = """
flavor={{flavor}}
servings=0
side=False
type=pie
""".lstrip()
assert actual == expected


def test_print_config_section_ini_missing_section():
config_obj = config.INIConfig(fixture_path("simple3.ini"))
section = ["sandwich"]
msg = "Bad config path: sandwich"
with raises(UWConfigError) as e:
config.print_config_section(config_obj.data, section, log=Logger())
assert msg in str(e.value)


def test_print_config_section_yaml(capsys):
config_obj = config.YAMLConfig(fixture_path("FV3_GFS_v16.yaml"))
section = ["sgs_tke", "profile_type"]
config.print_config_section(config_obj.data, section, log=Logger())
actual = capsys.readouterr().out
expected = """
name=fixed
surface_value=0.0
""".lstrip()
assert actual == expected


def test_print_config_section_yaml_for_nonscalar():
config_obj = config.YAMLConfig(fixture_path("FV3_GFS_v16.yaml"))
section = ["o3mr"]
with raises(UWConfigError) as e:
config.print_config_section(config_obj.data, section, log=Logger())
assert "Non-scalar value" in str(e.value)


def test_print_config_section_yaml_list():
config_obj = config.YAMLConfig(fixture_path("srw_example.yaml"))
section = ["FV3GFS", "nomads", "file_names", "grib2", "anl"]
with raises(UWConfigError) as e:
config.print_config_section(config_obj.data, section, log=Logger())
assert "must be a dictionary" in str(e.value)


def test_print_config_section_yaml_not_dict():
config_obj = config.YAMLConfig(fixture_path("FV3_GFS_v16.yaml"))
section = ["sgs_tke", "units"]
with raises(UWConfigError) as e:
config.print_config_section(config_obj.data, section, log=Logger())
assert "must be a dictionary" in str(e.value)


def test__log_and_error():
with raises(UWConfigError) as e:
config._log_and_error("Must be scalar value", log=Logger())
assert "Must be scalar value" in str(e.value)

0 comments on commit c2705b4

Please sign in to comment.