Add file and line annotation to strings when loading yaml (#103586)

This commit is contained in:
Erik Montnemery 2023-11-07 17:10:15 +01:00 committed by GitHub
parent 6276c4483c
commit 05deae09fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 1 deletions

View file

@ -4,7 +4,7 @@ from typing import Any
import yaml
from .objects import Input, NodeDictClass, NodeListClass
from .objects import Input, NodeDictClass, NodeListClass, NodeStrClass
# mypy: allow-untyped-calls, no-warn-return-any
@ -84,6 +84,11 @@ add_representer(
lambda dumper, value: dumper.represent_sequence("tag:yaml.org,2002:seq", value),
)
add_representer(
NodeStrClass,
lambda dumper, value: dumper.represent_scalar("tag:yaml.org,2002:str", str(value)),
)
add_representer(
Input,
lambda dumper, value: dumper.represent_scalar("!input", value.name),

View file

@ -371,6 +371,16 @@ def _construct_seq(loader: LoaderType, node: yaml.nodes.Node) -> JSON_TYPE:
return _add_reference(obj, loader, node)
def _handle_scalar_tag(
loader: LoaderType, node: yaml.nodes.ScalarNode
) -> str | int | float | None:
"""Add line number and file name to Load YAML sequence."""
obj = loader.construct_scalar(node)
if not isinstance(obj, str):
return obj
return _add_reference(obj, loader, node)
def _env_var_yaml(loader: LoaderType, node: yaml.nodes.Node) -> str:
"""Load environment variables and embed it into the configuration YAML."""
args = node.value.split()
@ -400,6 +410,7 @@ def add_constructor(tag: Any, constructor: Any) -> None:
add_constructor("!include", _include_yaml)
add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _handle_mapping_tag)
add_constructor(yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG, _handle_scalar_tag)
add_constructor(yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, _construct_seq)
add_constructor("!env_var", _env_var_yaml)
add_constructor("!secret", secret_yaml)

View file

@ -547,3 +547,36 @@ async def test_loading_actual_file_with_syntax(
"fixtures", "bad.yaml.txt"
)
await hass.async_add_executor_job(load_yaml_config_file, fixture_path)
def test_string_annotated(try_both_loaders) -> None:
"""Test strings are annotated with file + line."""
conf = (
"key1: str\n"
"key2:\n"
" blah: blah\n"
"key3:\n"
" - 1\n"
" - 2\n"
" - 3\n"
"key4: yes\n"
"key5: 1\n"
"key6: 1.0\n"
)
expected_annotations = {
"key1": [("<file>", 0), ("<file>", 0)],
"key2": [("<file>", 1), ("<file>", 2)],
"key3": [("<file>", 3), ("<file>", 4)],
"key4": [("<file>", 7), (None, None)],
"key5": [("<file>", 8), (None, None)],
"key6": [("<file>", 9), (None, None)],
}
with io.StringIO(conf) as file:
doc = yaml_loader.parse_yaml(file)
for key, value in doc.items():
assert getattr(key, "__config_file__", None) == expected_annotations[key][0][0]
assert getattr(key, "__line__", None) == expected_annotations[key][0][1]
assert (
getattr(value, "__config_file__", None) == expected_annotations[key][1][0]
)
assert getattr(value, "__line__", None) == expected_annotations[key][1][1]