mirror of
https://github.com/esphome/esphome.git
synced 2026-09-16 01:28:39 +00:00
[substitutions] Lock in shape check and document single-shot resolve
- Add unit tests asserting cv.Invalid when `substitutions: !include list.yaml` resolves to a non-mapping, covering both do_substitution_pass and do_packages_pass. - Note in resolve_substitutions_block that the resolve is single-shot and chained top-level includes are not supported (matches _walk_packages for `packages: !include`).
This commit is contained in:
@@ -426,6 +426,9 @@ def resolve_substitutions_block(
|
||||
``substitutions: !include ${var}.yaml`` can reference CLI-provided vars.
|
||||
"""
|
||||
if isinstance(substitutions, IncludeFile):
|
||||
# Single-shot resolution — matches ``_walk_packages`` for the
|
||||
# ``packages: !include`` entry point. Chained includes (an include that
|
||||
# itself loads another ``!include`` at the top level) are not supported.
|
||||
substitutions, _ = resolve_include(
|
||||
substitutions,
|
||||
[],
|
||||
|
||||
@@ -675,6 +675,57 @@ def test_include_filename_substitution_undefined_var(tmp_path: Path) -> None:
|
||||
substitutions.do_substitution_pass(config)
|
||||
|
||||
|
||||
def test_do_substitution_pass_included_substitutions_must_be_mapping(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""`substitutions: !include list.yaml` where the file holds a list raises cv.Invalid.
|
||||
|
||||
Locks in the shape check that runs after the deferred IncludeFile has been
|
||||
resolved.
|
||||
"""
|
||||
parent = tmp_path / "main.yaml"
|
||||
parent.write_text("")
|
||||
|
||||
def loader(path: Path):
|
||||
return ["not", "a", "mapping"]
|
||||
|
||||
include = yaml_util.IncludeFile(parent, "subs.yaml", None, loader)
|
||||
config = OrderedDict({CONF_SUBSTITUTIONS: include})
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid, match="Substitutions must be a key to value mapping"
|
||||
):
|
||||
substitutions.do_substitution_pass(config)
|
||||
|
||||
|
||||
def test_do_packages_pass_included_substitutions_must_be_mapping(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""`substitutions: !include list.yaml` alongside `packages:` raises cv.Invalid.
|
||||
|
||||
Without the shape check, ``UserDict(...)`` would surface a low-level
|
||||
``TypeError``; the explicit ``cv.Invalid`` points at the substitutions path.
|
||||
"""
|
||||
parent = tmp_path / "main.yaml"
|
||||
parent.write_text("")
|
||||
|
||||
def loader(path: Path):
|
||||
return ["not", "a", "mapping"]
|
||||
|
||||
include = yaml_util.IncludeFile(parent, "subs.yaml", None, loader)
|
||||
config = OrderedDict(
|
||||
{
|
||||
CONF_SUBSTITUTIONS: include,
|
||||
"packages": {"noop": {"wifi": {"ssid": "main"}}},
|
||||
}
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid, match="Substitutions must be a key to value mapping"
|
||||
):
|
||||
do_packages_pass(config)
|
||||
|
||||
|
||||
def test_resolve_package_undefined_var_in_include_filename(tmp_path: Path) -> None:
|
||||
"""An undefined substitution in a package include filename raises cv.Invalid.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user