Skip to content

Commit

Permalink
UW-549 Remove --partial option from template render mode (ufs-communi…
Browse files Browse the repository at this point in the history
  • Loading branch information
maddenp-noaa authored Apr 2, 2024
1 parent 15e5fe8 commit 4cd67ad
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 83 deletions.
31 changes: 22 additions & 9 deletions docs/sections/user_guide/cli/tools/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,16 @@ and an additional supplemental YAML file ``values2.yaml`` with the following con
Shell redirection via ``|``, ``>``, et al. may also be used to stream output to a file, another process, etc.

* Values in the primary input file can be overridden via one or more supplemental files specified as positional arguments, each overriding the last, or by environment variables, which have the highest precedence.
* Values in the input file can be overridden via one or more supplemental files specified as positional arguments. Priority increases from left to right.

.. code-block:: text
$ recipient=Sun uw config realize --input-file config.yaml --output-format yaml values1.yaml values2.yaml
$ uw config realize --input-file config.yaml --output-format yaml values1.yaml values2.yaml
values:
date: 20240105
empty: false
greeting: Good Night
message: 'Good Night Sun Good Night Sun Good Night Sun '
message: 'Good Night Moon Good Night Moon Good Night Moon '
recipient: Moon
repeat: 3
Expand All @@ -285,32 +285,45 @@ and an additional supplemental YAML file ``values2.yaml`` with the following con
recipient: Moon
repeat: 2
* By default, variables/expressions that cannot be rendered are passed through unchanged in the output. For example, config file ``config.yaml`` with contents
* By default, variables/expressions that cannot be rendered are passed through unchanged in the output. For example, given config file ``config.yaml`` with contents

.. code-block:: yaml
roses: "{{ color1 }}"
violets: "{{ color2 }}"
could normally be partially realized, with ``uw`` exiting with success status, by providing one of the two needed template values:
color1: red
.. code-block:: text
$ color1=red uw config realize --input-file config.yaml --output-format yaml
$ uw config realize --input-file config.yaml --output-format yaml values.yaml
roses: red
violets: '{{ color2 }}'
color1: red
$ echo $?
0
Adding the ``--total`` flag, however, requires ``uw`` to totally realize the config, and to exit with error status if it cannot:

.. code-block:: text
$ color1=red uw config realize --input-file config.yaml --output-format yaml --total
[2024-02-28T23:21:16] ERROR Config could not be realized. Try again with --values-needed for details.
$ uw config realize --input-file config.yaml --output-format yaml values.yaml --total
[2024-04-02T00:53:04] ERROR Config could not be realized. Try with --values-needed for details.
$ echo $?
1
* Realization of individual values is all-or-nothing. If a single value contains a mix of renderable and unrenderable variables/expressions, then the entire value remains unrealized. For example, given ``config.yaml`` with contents

.. code-block:: yaml
roses: "{{ color1 }} or {{ color2 }}"
color1: red
.. code-block:: text
$ uw config realize --input-file config.yaml --output-format yaml
roses: '{{ color1 }} or {{ color2 }}'
color1: red
* With the ``--dry-run`` flag specified, nothing is written to ``stdout`` (or to a file if ``--output-file`` is specified), but a report of what would have been written is logged to ``stderr``:

.. code-block:: text
Expand Down
13 changes: 2 additions & 11 deletions docs/sections/user_guide/cli/tools/template.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ The ``uw`` mode for handling :jinja2:`Jinja2 templates<templates>`.
$ uw template render --help
usage: uw template render [-h] [--version] [--input-file PATH] [--output-file PATH]
[--values-file PATH] [--values-format {ini,nml,sh,yaml}] [--env]
[--search-path PATH[:PATH:...]] [--values-needed] [--partial]
[--dry-run] [--quiet] [--verbose]
[--search-path PATH[:PATH:...]] [--values-needed] [--dry-run] [--quiet]
[--verbose]
[KEY=VALUE ...]
Render a template
Expand All @@ -58,8 +58,6 @@ The ``uw`` mode for handling :jinja2:`Jinja2 templates<templates>`.
Colon-separated paths to search for extra templates
--values-needed
Print report of values needed to render template
--partial
Permit partial template rendering
--dry-run
Only log info, making no changes
--quiet, -q
Expand Down Expand Up @@ -204,13 +202,6 @@ and a YAML file called ``values.yaml`` with the following contents:
[2024-03-02T16:42:48] ERROR recipient
[2024-03-02T16:42:48] ERROR Template could not be rendered
But the ``--partial`` switch may be used to render as much as possible while passing expressions containing missing values through unchanged:

.. code-block:: text
$ uw template render --input-file template --values-file values.yaml --partial
Hello, {{ recipient }}!
Values may also be supplemented by ``key=value`` command-line arguments:

.. code-block:: text
Expand Down
4 changes: 0 additions & 4 deletions src/uwtools/api/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def render(
env: bool = False,
searchpath: Optional[List[str]] = None,
values_needed: bool = False,
partial: bool = False,
dry_run: bool = False,
) -> str:
"""
Expand All @@ -42,7 +41,6 @@ def render(
:param env: Supplement values with environment variables?
:param searchpath: Paths to search for extra templates
:param values_needed: Just report variables needed to render the template?
:param partial: Permit unrendered Jinja2 variables/expressions in output?
:param dry_run: Run in dry-run mode?
:return: The rendered template string
:raises: UWTemplateRenderError if template could not be rendered
Expand All @@ -56,7 +54,6 @@ def render(
env=env,
searchpath=searchpath,
values_needed=values_needed,
partial=partial,
dry_run=dry_run,
)
if result is None:
Expand All @@ -72,7 +69,6 @@ def render_to_str( # pylint: disable=unused-argument
env: bool = False,
searchpath: Optional[List[str]] = None,
values_needed: bool = False,
partial: bool = False,
dry_run: bool = False,
) -> str:
"""
Expand Down
10 changes: 0 additions & 10 deletions src/uwtools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,6 @@ def _add_subparser_template_render(subparsers: Subparsers) -> ActionChecks:
_add_arg_env(optional)
_add_arg_search_path(optional)
_add_arg_values_needed(optional)
_add_arg_partial(optional)
_add_arg_dry_run(optional)
checks = _add_args_verbosity(optional)
_add_arg_key_eq_val_pairs(optional)
Expand Down Expand Up @@ -749,7 +748,6 @@ def _dispatch_template_render(args: Args) -> bool:
env=args[STR.env],
searchpath=args[STR.searchpath],
values_needed=args[STR.valsneeded],
partial=args[STR.partial],
dry_run=args[STR.dryrun],
)
except UWTemplateRenderError:
Expand Down Expand Up @@ -968,14 +966,6 @@ def _add_arg_output_format(group: Group, choices: List[str], required: bool = Fa
)


def _add_arg_partial(group: Group) -> None:
group.add_argument(
_switch(STR.partial),
action="store_true",
help="Permit partial template rendering",
)


def _add_arg_quiet(group: Group) -> None:
group.add_argument(
_switch(STR.quiet),
Expand Down
37 changes: 12 additions & 25 deletions src/uwtools/config/jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@
from pathlib import Path
from typing import Dict, List, Optional, Set, Union

from jinja2 import (
DebugUndefined,
Environment,
FileSystemLoader,
StrictUndefined,
Template,
Undefined,
meta,
)
from jinja2 import Environment, FileSystemLoader, StrictUndefined, Undefined, meta
from jinja2.exceptions import UndefinedError

from uwtools.config.support import UWYAMLConvert, UWYAMLRemove, format_to_config
Expand Down Expand Up @@ -164,7 +156,6 @@ def render(
env: bool = False,
searchpath: Optional[List[str]] = None,
values_needed: bool = False,
partial: bool = False,
dry_run: bool = False,
) -> Optional[str]:
"""
Expand All @@ -178,7 +169,6 @@ def render(
:param env: Supplement values with environment variables?
:param searchpath: Paths to search for extra templates.
:param values_needed: Just report variables needed to render the template?
:param partial: Permit unrendered Jinja2 variables/expressions in output?
:param dry_run: Run in dry-run mode?
:return: The rendered template, or None.
"""
Expand All @@ -196,21 +186,18 @@ def render(
_values_needed(undeclared_variables)
return None

# If partial rendering has been requested, do a best-effort render. Otherwise, report any
# missing values and return an error to the caller.
# Render the template. If there are missing values, report them and return an error to the
# caller.

if partial:
rendered = Template(str(template), undefined=DebugUndefined).render(values)
else:
missing = [var for var in undeclared_variables if var not in values.keys()]
if missing:
_log_missing_values(missing)
return None
try:
rendered = template.render()
except UndefinedError as e:
log.error("Render failed with error: %s", str(e))
return None
missing = [var for var in undeclared_variables if var not in values.keys()]
if missing:
_log_missing_values(missing)
return None
try:
rendered = template.render()
except UndefinedError as e:
log.error("Render failed with error: %s", str(e))
return None

# Log (dry-run mode) or write the rendered template.

Expand Down
1 change: 0 additions & 1 deletion src/uwtools/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ class STR:
mpasinit: str = "mpas_init"
outfile: str = "output_file"
outfmt: str = "output_format"
partial: str = "partial"
quiet: str = "quiet"
realize: str = "realize"
render: str = "render"
Expand Down
1 change: 0 additions & 1 deletion src/uwtools/tests/api/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def kwargs():
"overrides": {"key": "val"},
"searchpath": None,
"env": True,
"partial": True,
"values_needed": True,
"dry_run": True,
}
Expand Down
22 changes: 8 additions & 14 deletions src/uwtools/tests/config/test_jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,20 +249,6 @@ def test_render_fails(caplog, tmp_path):
assert logged(caplog, "Render failed with error: 'dict object' has no attribute 'e'")


@pytest.mark.parametrize("partial", [False, True])
def test_render_partial(caplog, capsys, partial):
log.setLevel(logging.INFO)
template = StringIO(initial_value="{{ greeting }} {{ recipient }}")
with patch.object(jinja2, "readable") as readable:
readable.return_value.__enter__.return_value = template
jinja2.render(values_src={"greeting": "Hello"}, partial=partial)
if partial:
assert "Hello {{ recipient }}" in capsys.readouterr().out
else:
assert logged(caplog, "Required value(s) not provided:")
assert logged(caplog, " recipient")


def test_render_values_missing(caplog, template_file, values_file):
log.setLevel(logging.INFO)
# Read in the config, remove the "roses" key, then re-write it.
Expand Down Expand Up @@ -551,3 +537,11 @@ def test_undeclared_variables(self):
s = "{{ a }} {{ b.c }} {{ d.e.f[g] }} {{ h[i] }} {{ j[88] }} {{ k|default(l) }}"
uvs = {"a", "b", "d", "g", "h", "i", "j", "k", "l"}
assert J2Template(values={}, template_source=s).undeclared_variables == uvs

def test__template_str(self, testdata):
obj = J2Template(values=testdata.config, template_source=testdata.template)
assert obj._template_str == "{{greeting}} to {{recipient}}"

def test___repr__(self, testdata):
obj = J2Template(values=testdata.config, template_source=testdata.template)
assert str(obj) == "{{greeting}} to {{recipient}}"
11 changes: 3 additions & 8 deletions src/uwtools/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,7 @@ def test__dispatch_template_render_fail(valsneeded):
STR.env: 5,
STR.searchpath: 6,
STR.valsneeded: valsneeded,
STR.partial: 7,
STR.dryrun: 8,
STR.dryrun: 7,
}
with patch.object(uwtools.api.template, "render", side_effect=UWTemplateRenderError):
assert cli._dispatch_template_render(args) is valsneeded
Expand All @@ -615,7 +614,6 @@ def test__dispatch_template_render_no_optional():
STR.env: False,
STR.searchpath: None,
STR.valsneeded: False,
STR.partial: False,
STR.dryrun: False,
}
with patch.object(uwtools.api.template, "render") as render:
Expand All @@ -629,7 +627,6 @@ def test__dispatch_template_render_no_optional():
env=False,
searchpath=None,
values_needed=False,
partial=False,
dry_run=False,
)

Expand All @@ -644,8 +641,7 @@ def test__dispatch_template_render_yaml():
STR.env: 5,
STR.searchpath: 6,
STR.valsneeded: 7,
STR.partial: 8,
STR.dryrun: 9,
STR.dryrun: 8,
}
with patch.object(uwtools.api.template, "render") as render:
cli._dispatch_template_render(args)
Expand All @@ -658,8 +654,7 @@ def test__dispatch_template_render_yaml():
env=5,
searchpath=6,
values_needed=7,
partial=8,
dry_run=9,
dry_run=8,
)


Expand Down

0 comments on commit 4cd67ad

Please sign in to comment.