From 162bb98bd01695738813753922decfd2e59e2214 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Mon, 8 Jul 2024 19:44:35 +0000 Subject: [PATCH 1/9] Let realize_to_dict() call realize() --- src/uwtools/api/config.py | 9 ++++++--- src/uwtools/tests/api/test_config.py | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/uwtools/api/config.py b/src/uwtools/api/config.py index b8e91faed..bda046e94 100644 --- a/src/uwtools/api/config.py +++ b/src/uwtools/api/config.py @@ -124,14 +124,14 @@ def realize( total: bool = False, dry_run: bool = False, stdin_ok: bool = False, -) -> None: +) -> dict: """ NB: This docstring is dynamically replaced: See realize.__doc__ definition below. """ input_config = ( _YAMLConfig(config=input_config) if isinstance(input_config, dict) else input_config ) - _realize( + return _realize( input_config=_ensure_data_source(input_config, stdin_ok), input_format=input_format, update_config=_ensure_data_source(update_config, stdin_ok), @@ -150,7 +150,9 @@ def realize_to_dict( # pylint: disable=unused-argument input_format: Optional[str] = None, update_config: Optional[Union[dict, _Config, Path, str]] = None, update_format: Optional[str] = None, + key_path: Optional[list[Union[str, int]]] = None, values_needed: bool = False, + total: bool = False, dry_run: bool = False, stdin_ok: bool = False, ) -> dict: @@ -159,7 +161,7 @@ def realize_to_dict( # pylint: disable=unused-argument See ``realize()`` for details on arguments, etc. """ - return _realize(**{**locals(), "output_file": Path(os.devnull), "output_format": None}) + return realize(**{**locals(), "output_file": Path(os.devnull), "output_format": None}) def validate( @@ -240,6 +242,7 @@ def validate( :param total: Require rendering of all Jinja2 variables/expressions :param dry_run: Log output instead of writing to output :param stdin_ok: OK to read from ``stdin``? +:return: The ``dict`` representation of the realized config :raises: UWConfigRealizeError if ``total`` is ``True`` and any Jinja2 variable/expression was not rendered """.format( extensions=", ".join(_FORMAT.extensions()) diff --git a/src/uwtools/tests/api/test_config.py b/src/uwtools/tests/api/test_config.py index 8336f6c6b..1a16754cf 100644 --- a/src/uwtools/tests/api/test_config.py +++ b/src/uwtools/tests/api/test_config.py @@ -77,13 +77,15 @@ def test_realize_to_dict(): "input_format": "fmt1", "update_config": None, "update_format": None, + "key_path": None, "values_needed": True, + "total": False, "dry_run": False, "stdin_ok": False, } - with patch.object(config, "_realize") as _realize: + with patch.object(config, "realize") as realize: config.realize_to_dict(**kwargs) - _realize.assert_called_once_with( + realize.assert_called_once_with( **dict({**kwargs, **{"output_file": Path(os.devnull), "output_format": None}}) ) From cd1101c60d50a5bd99979c241d96356f3829757a Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 04:59:40 +0000 Subject: [PATCH 2/9] WIP --- src/uwtools/api/config.py | 9 ++---- src/uwtools/config/tools.py | 37 +++++++++++++------------ src/uwtools/tests/config/test_tools.py | 38 +++++++++----------------- src/uwtools/utils/api.py | 35 +++++++++++++++++++----- 4 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/uwtools/api/config.py b/src/uwtools/api/config.py index bda046e94..3b5b51fb4 100644 --- a/src/uwtools/api/config.py +++ b/src/uwtools/api/config.py @@ -113,9 +113,9 @@ def get_yaml_config( def realize( - input_config: Optional[Union[dict, _Config, Path, str]] = None, + input_config: Optional[Union[_Config, Path, dict, str]] = None, input_format: Optional[str] = None, - update_config: Optional[Union[dict, _Config, Path, str]] = None, + update_config: Optional[Union[_Config, Path, dict, str]] = None, update_format: Optional[str] = None, output_file: Optional[Union[Path, str]] = None, output_format: Optional[str] = None, @@ -128,9 +128,6 @@ def realize( """ NB: This docstring is dynamically replaced: See realize.__doc__ definition below. """ - input_config = ( - _YAMLConfig(config=input_config) if isinstance(input_config, dict) else input_config - ) return _realize( input_config=_ensure_data_source(input_config, stdin_ok), input_format=input_format, @@ -181,7 +178,7 @@ def validate( :return: ``True`` if the YAML file conforms to the schema, ``False`` otherwise """ return _validate_yaml( - schema_file=_ensure_data_source(schema_file, stdin_ok), config=_str2path(config) + schema_file=_str2path(schema_file), config=_ensure_data_source(config, stdin_ok) ) diff --git a/src/uwtools/config/tools.py b/src/uwtools/config/tools.py index ab86472a7..f98bd29d0 100644 --- a/src/uwtools/config/tools.py +++ b/src/uwtools/config/tools.py @@ -76,9 +76,9 @@ def config_check_depths_update(config_obj: Union[Config, dict], target_format: s def realize_config( - input_config: Union[Config, Optional[Path]] = None, + input_config: Optional[Union[Config, Path, dict]] = None, input_format: Optional[str] = None, - update_config: Union[Config, Optional[Path]] = None, + update_config: Optional[Union[Config, Path, dict]] = None, update_format: Optional[str] = None, output_file: Optional[Path] = None, output_format: Optional[str] = None, @@ -90,7 +90,7 @@ def realize_config( """ NB: This docstring is dynamically replaced: See realize_config.__doc__ definition below. """ - input_obj, input_format = _realize_config_input_setup(input_config, input_format) + input_obj = _realize_config_input_setup(input_config, input_format) input_obj = _realize_config_update(input_obj, update_config, update_format) input_obj.dereference() output_data, output_format = _realize_config_output_setup( @@ -114,7 +114,7 @@ def realize_config( def _ensure_format( - desc: str, fmt: Optional[str] = None, config: Union[Config, Optional[Path]] = None + desc: str, fmt: Optional[str] = None, config: Optional[Union[Config, Path, dict]] = None ) -> str: """ Return the given format, or the appropriate format as deduced from the config. @@ -127,11 +127,14 @@ def _ensure_format( """ if isinstance(config, Config): return config.get_format() + if isinstance(config, Path): + return fmt or get_file_format(config) + if isinstance(config, dict): + if fmt is None: + raise UWError(f"Must specify {desc} format when {desc} config is a dict") + return fmt if fmt is None: - if config is not None: - fmt = get_file_format(config) - else: - raise UWError(f"Either {desc} file format or name must be specified") + raise UWError(f"Either {desc} path or format name must be specified") return fmt @@ -161,24 +164,22 @@ def _print_config_section(config: dict, key_path: list[str]) -> None: def _realize_config_input_setup( - input_config: Union[Config, Optional[Path]] = None, input_format: Optional[str] = None -) -> tuple[Config, str]: + input_config: Optional[Union[Config, Path, dict]] = None, input_format: Optional[str] = None +) -> Config: """ Set up config-realize input. :param input_config: Input config source (None => read stdin). :param input_format: Format of the input config. - :return: The input Config object and its format name. + :return: The input Config object. """ + if isinstance(input_config, Config): + return input_config input_format = _ensure_format("input", input_format, input_config) if not input_config: log.debug("Reading input from stdin") - input_obj: Config = ( - input_config - if isinstance(input_config, Config) - else format_to_config(input_format)(config=input_config) - ) - return input_obj, input_format + config_obj: Config = format_to_config(input_format)(config=input_config) + return config_obj def _realize_config_output_setup( @@ -209,7 +210,7 @@ def _realize_config_output_setup( def _realize_config_update( input_obj: Config, - update_config: Union[Config, Optional[Path]] = None, + update_config: Optional[Union[Config, Path, dict]] = None, update_format: Optional[str] = None, ) -> Config: """ diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index 2ea860181..8e6bec0bc 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -523,7 +523,7 @@ def test_realize_config_values_needed_yaml(caplog): def test__ensure_format_bad(): with raises(UWError) as e: tools._ensure_format(desc="foo") - assert str(e.value) == "Either foo file format or name must be specified" + assert str(e.value) == "Either foo path or format name must be specified" def test__ensure_format_config_obj(): @@ -607,9 +607,8 @@ def test__print_config_section_yaml_not_dict(): def test__realize_config_input_setup_ini_cfgobj(): data = {"section": {"foo": "bar"}} cfgobj = INIConfig(config=data) - input_obj, input_format = tools._realize_config_input_setup(input_config=cfgobj) + input_obj = tools._realize_config_input_setup(input_config=cfgobj) assert input_obj.data == data - assert input_format == FORMAT.ini def test__realize_config_input_setup_ini_file(tmp_path): @@ -620,9 +619,8 @@ def test__realize_config_input_setup_ini_file(tmp_path): path = tmp_path / "config.ini" with open(path, "w", encoding="utf-8") as f: print(dedent(data).strip(), file=f) - input_obj, input_format = tools._realize_config_input_setup(input_config=path) + input_obj = tools._realize_config_input_setup(input_config=path) assert input_obj.data == {"section": {"foo": "bar"}} - assert input_format == FORMAT.ini def test__realize_config_input_setup_ini_stdin(caplog): @@ -637,18 +635,16 @@ def test__realize_config_input_setup_ini_stdin(caplog): print(dedent(data).strip(), file=s) s.seek(0) with patch.object(sys, "stdin", new=s): - input_obj, input_format = tools._realize_config_input_setup(input_format=FORMAT.ini) + input_obj = tools._realize_config_input_setup(input_format=FORMAT.ini) assert input_obj.data == {"section": {"foo": "bar", "baz": "88"}} # note: 88 is str, not int - assert input_format == FORMAT.ini assert logged(caplog, "Reading input from stdin") def test__realize_config_input_setup_nml_cfgobj(): data = {"nl": {"pi": 3.14}} cfgobj = NMLConfig(config=data) - input_obj, input_format = tools._realize_config_input_setup(input_config=cfgobj) + input_obj = tools._realize_config_input_setup(input_config=cfgobj) assert input_obj.data == data - assert input_format == FORMAT.nml def test__realize_config_input_setup_nml_file(tmp_path): @@ -660,9 +656,8 @@ def test__realize_config_input_setup_nml_file(tmp_path): path = tmp_path / "config.nml" with open(path, "w", encoding="utf-8") as f: print(dedent(data).strip(), file=f) - input_obj, input_format = tools._realize_config_input_setup(input_config=path) + input_obj = tools._realize_config_input_setup(input_config=path) assert input_obj["nl"]["pi"] == 3.14 - assert input_format == FORMAT.nml def test__realize_config_input_setup_nml_stdin(caplog): @@ -677,18 +672,16 @@ def test__realize_config_input_setup_nml_stdin(caplog): print(dedent(data).strip(), file=s) s.seek(0) with patch.object(sys, "stdin", new=s): - input_obj, input_format = tools._realize_config_input_setup(input_format=FORMAT.nml) + input_obj = tools._realize_config_input_setup(input_format=FORMAT.nml) assert input_obj["nl"]["pi"] == 3.14 - assert input_format == FORMAT.nml assert logged(caplog, "Reading input from stdin") def test__realize_config_input_setup_sh_cfgobj(): data = {"foo": "bar"} cfgobj = SHConfig(config=data) - input_obj, input_format = tools._realize_config_input_setup(input_config=cfgobj) + input_obj = tools._realize_config_input_setup(input_config=cfgobj) assert input_obj.data == data - assert input_format == FORMAT.sh def test__realize_config_input_setup_sh_file(tmp_path): @@ -698,9 +691,8 @@ def test__realize_config_input_setup_sh_file(tmp_path): path = tmp_path / "config.sh" with open(path, "w", encoding="utf-8") as f: print(dedent(data).strip(), file=f) - input_obj, input_format = tools._realize_config_input_setup(input_config=path) + input_obj = tools._realize_config_input_setup(input_config=path) assert input_obj.data == {"foo": "bar"} - assert input_format == FORMAT.sh def test__realize_config_input_setup_sh_stdin(caplog): @@ -713,18 +705,16 @@ def test__realize_config_input_setup_sh_stdin(caplog): print(dedent(data).strip(), file=s) s.seek(0) with patch.object(sys, "stdin", new=s): - input_obj, input_format = tools._realize_config_input_setup(input_format=FORMAT.sh) + input_obj = tools._realize_config_input_setup(input_format=FORMAT.sh) assert input_obj.data == {"foo": "bar"} - assert input_format == FORMAT.sh assert logged(caplog, "Reading input from stdin") def test__realize_config_input_setup_yaml_cfgobj(): data = {"foo": "bar"} cfgobj = YAMLConfig(config=data) - input_obj, input_format = tools._realize_config_input_setup(input_config=cfgobj) + input_obj = tools._realize_config_input_setup(input_config=cfgobj) assert input_obj.data == data - assert input_format == FORMAT.yaml def test__realize_config_input_setup_yaml_file(tmp_path): @@ -734,9 +724,8 @@ def test__realize_config_input_setup_yaml_file(tmp_path): path = tmp_path / "config.yaml" with open(path, "w", encoding="utf-8") as f: print(dedent(data).strip(), file=f) - input_obj, input_format = tools._realize_config_input_setup(input_config=path) + input_obj = tools._realize_config_input_setup(input_config=path) assert input_obj.data == {"foo": "bar"} - assert input_format == FORMAT.yaml def test__realize_config_input_setup_yaml_stdin(caplog): @@ -749,9 +738,8 @@ def test__realize_config_input_setup_yaml_stdin(caplog): print(dedent(data).strip(), file=s) s.seek(0) with patch.object(sys, "stdin", new=s): - input_obj, input_format = tools._realize_config_input_setup(input_format=FORMAT.yaml) + input_obj = tools._realize_config_input_setup(input_format=FORMAT.yaml) assert input_obj.data == {"foo": "bar"} - assert input_format == FORMAT.yaml assert logged(caplog, "Reading input from stdin") diff --git a/src/uwtools/utils/api.py b/src/uwtools/utils/api.py index b18348b51..5959c53ab 100644 --- a/src/uwtools/utils/api.py +++ b/src/uwtools/utils/api.py @@ -7,29 +7,50 @@ from pathlib import Path from typing import Any, Callable, Optional, Union -from uwtools.config.formats.base import Config +# from uwtools.config.formats.base import Config from uwtools.drivers.driver import DriverT from uwtools.drivers.support import graph from uwtools.drivers.support import tasks as _tasks from uwtools.exceptions import UWError +# from functools import singledispatch +# from typing import TypeVar + + # Public -def ensure_data_source( - data_source: Optional[Union[dict, Config, Path, str]], stdin_ok: bool -) -> Any: +# @singledispatch +def ensure_data_source(data_source: Any, stdin_ok: bool) -> Any: """ - If stdin read is disabled, ensure that a data source was provided. Convert str -> Path. + If stdin read is disabled, ensure that a data source was provided. :param data_source: Data source as provided to API. :param stdin_ok: OK to read from stdin? - :return: Data source, with a str converted to Path. + :return: Data source. :raises: UWError if no data source was provided and stdin read is disabled. """ if data_source is None and not stdin_ok: raise UWError("Set stdin_ok=True to permit read from stdin") - return str2path(data_source) + if isinstance(data_source, str): + data_source = Path(data_source) + return data_source + + +# @ensure_data_source.register +# def _(data_source: str, stdin_ok: bool) -> Optional[str]: +# return ensure_data_source(data_source=Path(data_source), stdin_ok=stdin_ok) + +# def ensure_data_source( +# data_source: Optional[Union[dict, Config, Path, str]], stdin_ok: bool +# ) -> Optional[Union[dict, Path]]: +# if data_source is None and not stdin_ok: +# raise UWError("Set stdin_ok=True to permit read from stdin") +# if isinstance(data_source, Config): +# return data_source.data +# if isinstance(data_source, str): +# return Path(data_source) +# return data_source def make_execute( From 0a8e949dd3f096d1ca5aa075c448867a29a9eff8 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 05:06:49 +0000 Subject: [PATCH 3/9] Cleanup --- src/uwtools/utils/api.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/uwtools/utils/api.py b/src/uwtools/utils/api.py index 5959c53ab..a9147134c 100644 --- a/src/uwtools/utils/api.py +++ b/src/uwtools/utils/api.py @@ -13,14 +13,9 @@ from uwtools.drivers.support import tasks as _tasks from uwtools.exceptions import UWError -# from functools import singledispatch -# from typing import TypeVar - - # Public -# @singledispatch def ensure_data_source(data_source: Any, stdin_ok: bool) -> Any: """ If stdin read is disabled, ensure that a data source was provided. @@ -37,22 +32,6 @@ def ensure_data_source(data_source: Any, stdin_ok: bool) -> Any: return data_source -# @ensure_data_source.register -# def _(data_source: str, stdin_ok: bool) -> Optional[str]: -# return ensure_data_source(data_source=Path(data_source), stdin_ok=stdin_ok) - -# def ensure_data_source( -# data_source: Optional[Union[dict, Config, Path, str]], stdin_ok: bool -# ) -> Optional[Union[dict, Path]]: -# if data_source is None and not stdin_ok: -# raise UWError("Set stdin_ok=True to permit read from stdin") -# if isinstance(data_source, Config): -# return data_source.data -# if isinstance(data_source, str): -# return Path(data_source) -# return data_source - - def make_execute( driver_class: DriverT, with_cycle: Optional[bool] = False, From 44fb6c83248d21fc4a6aaae628826dfc63cf8086 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 05:08:58 +0000 Subject: [PATCH 4/9] Cleanup --- src/uwtools/utils/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uwtools/utils/api.py b/src/uwtools/utils/api.py index a9147134c..4b5e58ec8 100644 --- a/src/uwtools/utils/api.py +++ b/src/uwtools/utils/api.py @@ -7,7 +7,6 @@ from pathlib import Path from typing import Any, Callable, Optional, Union -# from uwtools.config.formats.base import Config from uwtools.drivers.driver import DriverT from uwtools.drivers.support import graph from uwtools.drivers.support import tasks as _tasks From b7467622fea3b04f8d337d6c05cfc79a0beba883 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 05:24:58 +0000 Subject: [PATCH 5/9] WIP --- src/uwtools/api/config.py | 16 ++++++++-------- src/uwtools/api/file.py | 9 +++++---- src/uwtools/api/rocoto.py | 6 ++++-- src/uwtools/api/template.py | 4 ++-- src/uwtools/tests/utils/test_api.py | 7 ------- src/uwtools/utils/api.py | 10 +++++----- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/uwtools/api/config.py b/src/uwtools/api/config.py index 3b5b51fb4..65060a541 100644 --- a/src/uwtools/api/config.py +++ b/src/uwtools/api/config.py @@ -50,7 +50,7 @@ def get_fieldtable_config( :param stdin_ok: OK to read from ``stdin``? :return: An initialized ``FieldTableConfig`` object """ - return _FieldTableConfig(config=_ensure_data_source(config, stdin_ok)) + return _FieldTableConfig(config=_ensure_data_source(_str2path(config), stdin_ok)) def get_ini_config( @@ -64,7 +64,7 @@ def get_ini_config( :param stdin_ok: OK to read from ``stdin``? :return: An initialized ``INIConfig`` object """ - return _INIConfig(config=_ensure_data_source(config, stdin_ok)) + return _INIConfig(config=_ensure_data_source(_str2path(config), stdin_ok)) def get_nml_config( @@ -79,7 +79,7 @@ def get_nml_config( :param stdin_ok: OK to read from ``stdin``? :return: An initialized ``NMLConfig`` object """ - return _NMLConfig(config=_ensure_data_source(config, stdin_ok)) + return _NMLConfig(config=_ensure_data_source(_str2path(config), stdin_ok)) def get_sh_config( @@ -94,7 +94,7 @@ def get_sh_config( :param stdin_ok: OK to read from ``stdin``? :return: An initialized ``SHConfig`` object """ - return _SHConfig(config=_ensure_data_source(config, stdin_ok)) + return _SHConfig(config=_ensure_data_source(_str2path(config), stdin_ok)) def get_yaml_config( @@ -109,7 +109,7 @@ def get_yaml_config( :param stdin_ok: OK to read from ``stdin``? :return: An initialized ``YAMLConfig`` object """ - return _YAMLConfig(config=_ensure_data_source(config, stdin_ok)) + return _YAMLConfig(config=_ensure_data_source(_str2path(config), stdin_ok)) def realize( @@ -129,9 +129,9 @@ def realize( NB: This docstring is dynamically replaced: See realize.__doc__ definition below. """ return _realize( - input_config=_ensure_data_source(input_config, stdin_ok), + input_config=_ensure_data_source(_str2path(input_config), stdin_ok), input_format=input_format, - update_config=_ensure_data_source(update_config, stdin_ok), + update_config=_ensure_data_source(_str2path(update_config), stdin_ok), update_format=update_format, output_file=_str2path(output_file), output_format=output_format, @@ -178,7 +178,7 @@ def validate( :return: ``True`` if the YAML file conforms to the schema, ``False`` otherwise """ return _validate_yaml( - schema_file=_str2path(schema_file), config=_ensure_data_source(config, stdin_ok) + schema_file=_str2path(schema_file), config=_ensure_data_source(_str2path(config), stdin_ok) ) diff --git a/src/uwtools/api/file.py b/src/uwtools/api/file.py index 48a8065f5..e6cfebb11 100644 --- a/src/uwtools/api/file.py +++ b/src/uwtools/api/file.py @@ -9,11 +9,12 @@ from uwtools.file import FileCopier as _FileCopier from uwtools.file import FileLinker as _FileLinker from uwtools.utils.api import ensure_data_source as _ensure_data_source +from uwtools.utils.api import str2path as _str2path def copy( target_dir: Union[Path, str], - config: Optional[Union[dict, Path, str]] = None, + config: Optional[Union[Path, dict, str]] = None, cycle: Optional[dt.datetime] = None, leadtime: Optional[dt.timedelta] = None, keys: Optional[list[str]] = None, @@ -34,7 +35,7 @@ def copy( """ _FileCopier( target_dir=Path(target_dir), - config=_ensure_data_source(config, stdin_ok), + config=_ensure_data_source(_str2path(config), stdin_ok), cycle=cycle, leadtime=leadtime, keys=keys, @@ -45,7 +46,7 @@ def copy( def link( target_dir: Union[Path, str], - config: Optional[Union[dict, Path, str]] = None, + config: Optional[Union[Path, dict, str]] = None, cycle: Optional[dt.datetime] = None, leadtime: Optional[dt.timedelta] = None, keys: Optional[list[str]] = None, @@ -66,7 +67,7 @@ def link( """ _FileLinker( target_dir=Path(target_dir), - config=_ensure_data_source(config, stdin_ok), + config=_ensure_data_source(_str2path(config), stdin_ok), cycle=cycle, leadtime=leadtime, keys=keys, diff --git a/src/uwtools/api/rocoto.py b/src/uwtools/api/rocoto.py index 2c25f873c..e8db86207 100644 --- a/src/uwtools/api/rocoto.py +++ b/src/uwtools/api/rocoto.py @@ -31,7 +31,9 @@ def realize( :param stdin_ok: OK to read from ``stdin``? :return: ``True`` """ - _realize(config=_ensure_data_source(config, stdin_ok), output_file=_str2path(output_file)) + _realize( + config=_ensure_data_source(_str2path(config), stdin_ok), output_file=_str2path(output_file) + ) return True @@ -46,4 +48,4 @@ def validate( :param stdin_ok: OK to read from ``stdin``? :return: ``True`` if the XML conforms to the schema, ``False`` otherwise """ - return _validate(xml_file=_ensure_data_source(xml_file, stdin_ok)) + return _validate(xml_file=_ensure_data_source(_str2path(xml_file), stdin_ok)) diff --git a/src/uwtools/api/template.py b/src/uwtools/api/template.py index 7f589ce08..ea12fe5e6 100644 --- a/src/uwtools/api/template.py +++ b/src/uwtools/api/template.py @@ -52,7 +52,7 @@ def render( result = _render( values_src=_str2path(values_src), values_format=values_format, - input_file=_ensure_data_source(input_file, stdin_ok), + input_file=_ensure_data_source(_str2path(input_file), stdin_ok), output_file=_str2path(output_file), overrides=overrides, env=env, @@ -104,7 +104,7 @@ def translate( :return: ``True`` """ _convert_atparse_to_jinja2( - input_file=_ensure_data_source(input_file, stdin_ok), + input_file=_ensure_data_source(_str2path(input_file), stdin_ok), output_file=_str2path(output_file), dry_run=dry_run, ) diff --git a/src/uwtools/tests/utils/test_api.py b/src/uwtools/tests/utils/test_api.py index a06c58b8a..45911ab55 100644 --- a/src/uwtools/tests/utils/test_api.py +++ b/src/uwtools/tests/utils/test_api.py @@ -40,13 +40,6 @@ def test_ensure_data_source_stdin_ok(): assert api.ensure_data_source(data_source=None, stdin_ok=True) is None -def test_ensure_data_source_str_to_path(): - val = "/some/path" - result = api.ensure_data_source(data_source=val, stdin_ok=False) - assert isinstance(result, Path) - assert result == Path(val) - - def test_make_execute(execute_kwargs): func = api.make_execute(driver_class=ConcreteDriver, with_cycle=False) assert func.__name__ == "execute" diff --git a/src/uwtools/utils/api.py b/src/uwtools/utils/api.py index 4b5e58ec8..180a7d791 100644 --- a/src/uwtools/utils/api.py +++ b/src/uwtools/utils/api.py @@ -5,17 +5,19 @@ import datetime as dt import re from pathlib import Path -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, Optional, TypeVar, Union from uwtools.drivers.driver import DriverT from uwtools.drivers.support import graph from uwtools.drivers.support import tasks as _tasks from uwtools.exceptions import UWError +T = TypeVar("T") + # Public -def ensure_data_source(data_source: Any, stdin_ok: bool) -> Any: +def ensure_data_source(data_source: T, stdin_ok: bool) -> T: """ If stdin read is disabled, ensure that a data source was provided. @@ -26,8 +28,6 @@ def ensure_data_source(data_source: Any, stdin_ok: bool) -> Any: """ if data_source is None and not stdin_ok: raise UWError("Set stdin_ok=True to permit read from stdin") - if isinstance(data_source, str): - data_source = Path(data_source) return data_source @@ -191,7 +191,7 @@ def _execute( :return: ``True`` if task completes without raising an exception. """ kwargs = dict( - config=ensure_data_source(config, stdin_ok), + config=ensure_data_source(str2path(config), stdin_ok), batch=batch, dry_run=dry_run, key_path=key_path, From 4ef52b0a324defe11dee3b84d53e61b9c3a629ca Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 05:29:05 +0000 Subject: [PATCH 6/9] Unit tests @ 100% --- src/uwtools/tests/config/test_tools.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index 8e6bec0bc..74c812335 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -520,16 +520,26 @@ def test_realize_config_values_needed_yaml(caplog): assert actual.strip() == dedent(expected).strip() -def test__ensure_format_bad(): +def test__ensure_format_bad_no_path_no_format(): with raises(UWError) as e: tools._ensure_format(desc="foo") assert str(e.value) == "Either foo path or format name must be specified" +def test__ensure_format_bad_dict_no_format(): + with raises(UWError) as e: + tools._ensure_format(desc="foo", config={}) + assert str(e.value) == "Must specify foo format when foo config is a dict" + + def test__ensure_format_config_obj(): assert tools._ensure_format(desc="foo", config=YAMLConfig(config={})) == FORMAT.yaml +def test__ensure_format_dict(): + assert tools._ensure_format(desc="foo", fmt=FORMAT.yaml, config={}) == FORMAT.yaml + + def test__ensure_format_deduced(): assert tools._ensure_format(desc="foo", config=Path("/tmp/config.yaml")) == FORMAT.yaml From ca15272afddd1a4be746b143fcac5649eac52908 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 17:57:42 +0000 Subject: [PATCH 7/9] Use yaml format for dict configs --- src/uwtools/config/tools.py | 4 +--- src/uwtools/tests/config/test_tools.py | 12 +++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/uwtools/config/tools.py b/src/uwtools/config/tools.py index f98bd29d0..26d62bc14 100644 --- a/src/uwtools/config/tools.py +++ b/src/uwtools/config/tools.py @@ -130,9 +130,7 @@ def _ensure_format( if isinstance(config, Path): return fmt or get_file_format(config) if isinstance(config, dict): - if fmt is None: - raise UWError(f"Must specify {desc} format when {desc} config is a dict") - return fmt + return fmt or FORMAT.yaml if fmt is None: raise UWError(f"Either {desc} path or format name must be specified") return fmt diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index 74c812335..86ba08a8b 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -526,20 +526,18 @@ def test__ensure_format_bad_no_path_no_format(): assert str(e.value) == "Either foo path or format name must be specified" -def test__ensure_format_bad_dict_no_format(): - with raises(UWError) as e: - tools._ensure_format(desc="foo", config={}) - assert str(e.value) == "Must specify foo format when foo config is a dict" - - def test__ensure_format_config_obj(): assert tools._ensure_format(desc="foo", config=YAMLConfig(config={})) == FORMAT.yaml -def test__ensure_format_dict(): +def test__ensure_format_dict_explicit(): assert tools._ensure_format(desc="foo", fmt=FORMAT.yaml, config={}) == FORMAT.yaml +def test__ensure_format_dict_implicit(): + assert tools._ensure_format(desc="foo", config={}) == FORMAT.yaml + + def test__ensure_format_deduced(): assert tools._ensure_format(desc="foo", config=Path("/tmp/config.yaml")) == FORMAT.yaml From 65dd9c265ccf5476ca97e005eecd6a1ad76ff17b Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Tue, 9 Jul 2024 18:11:51 +0000 Subject: [PATCH 8/9] WIP --- src/uwtools/api/config.py | 2 +- src/uwtools/tests/api/test_config.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uwtools/api/config.py b/src/uwtools/api/config.py index 65060a541..7201cfb61 100644 --- a/src/uwtools/api/config.py +++ b/src/uwtools/api/config.py @@ -158,7 +158,7 @@ def realize_to_dict( # pylint: disable=unused-argument See ``realize()`` for details on arguments, etc. """ - return realize(**{**locals(), "output_file": Path(os.devnull), "output_format": None}) + return realize(**{**locals(), "output_file": Path(os.devnull), "output_format": _FORMAT.yaml}) def validate( diff --git a/src/uwtools/tests/api/test_config.py b/src/uwtools/tests/api/test_config.py index 1a16754cf..e58744684 100644 --- a/src/uwtools/tests/api/test_config.py +++ b/src/uwtools/tests/api/test_config.py @@ -9,6 +9,7 @@ from uwtools.api import config from uwtools.config.formats.yaml import YAMLConfig +from uwtools.utils.file import FORMAT def test_compare(): @@ -86,7 +87,7 @@ def test_realize_to_dict(): with patch.object(config, "realize") as realize: config.realize_to_dict(**kwargs) realize.assert_called_once_with( - **dict({**kwargs, **{"output_file": Path(os.devnull), "output_format": None}}) + **dict({**kwargs, **{"output_file": Path(os.devnull), "output_format": FORMAT.yaml}}) ) From 8d5b7c18e96a819465aad61fe67a9aa70b298952 Mon Sep 17 00:00:00 2001 From: Paul Madden Date: Wed, 10 Jul 2024 16:24:17 +0000 Subject: [PATCH 9/9] Update tests based on PR comments --- src/uwtools/tests/config/test_tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index 86ba08a8b..94a3f0873 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -527,7 +527,8 @@ def test__ensure_format_bad_no_path_no_format(): def test__ensure_format_config_obj(): - assert tools._ensure_format(desc="foo", config=YAMLConfig(config={})) == FORMAT.yaml + config = NMLConfig({"nl": {"n": 88}}) + assert tools._ensure_format(desc="foo", config=config) == FORMAT.nml def test__ensure_format_dict_explicit(): @@ -539,7 +540,7 @@ def test__ensure_format_dict_implicit(): def test__ensure_format_deduced(): - assert tools._ensure_format(desc="foo", config=Path("/tmp/config.yaml")) == FORMAT.yaml + assert tools._ensure_format(desc="foo", config=Path("/tmp/config.nml")) == FORMAT.nml def test__ensure_format_explicitly_specified_no_path():