Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions automation_inspector/app/references.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@
"humidity_entity_id",
}

JINJA_CONTEXT_ROOTS = frozenset(
{
"config",
"context",
"item",
"loop",
"now",
"repeat",
"state",
"states",
"this",
"trigger",
"utcnow",
"value_json",
"variables",
"wait",
}
)

STANDARD_DOMAINS = frozenset(
{
"air_quality",
Expand Down Expand Up @@ -197,10 +216,15 @@ def scan_string(value: str, key: str | None) -> None:
template_matches = set(TEMPLATE_ENTITY_RE.findall(value))
for entity_id in template_matches:
add(entity_id, "template")
explicit_key = key in ENTITY_VALUE_KEYS
source = "template" if "{{" in value or "{%" in value else "configuration"
templated = _is_template(value)
# A templated value is resolved by Home Assistant at runtime, so an
# entity-shaped key cannot vouch for the dotted tokens it contains.
explicit_key = key in ENTITY_VALUE_KEYS and not templated
source = "template" if templated else "configuration"
for entity_id in ENTITY_ID_RE.findall(value):
domain = entity_id.split(".", 1)[0]
if templated and domain in JINJA_CONTEXT_ROOTS:
continue
if explicit_key or entity_id in template_matches or domain in domains:
add(entity_id, "explicit" if explicit_key else source)

Expand Down
61 changes: 61 additions & 0 deletions tests/test_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,64 @@ def test_ignores_entity_like_values_in_description() -> None:
references = collect_entity_references(config, set())

assert references == {"sensor.real_dependency": {"explicit"}}


def test_ignores_jinja_context_paths_but_keeps_literal_template_entities() -> None:
config = {
"conditions": [
{
"condition": "template",
"value_template": "{{ is_state('sensor.real_temperature', '20') }}",
}
],
"actions": [
{
"repeat": {
"for_each": [{"boolean": "input_boolean.window_pause"}],
"sequence": [
{
"action": "input_boolean.turn_off",
"target": {"entity_id": "{{ repeat.item.boolean }}"},
},
{
"action": "logbook.log",
"data": {"message": "Triggered by {{ trigger.entity_id }}"},
},
],
}
}
],
}

references = collect_entity_references(config, set())

assert references == {
"input_boolean.window_pause": {"configuration"},
"sensor.real_temperature": {"template"},
}


def test_keeps_template_entity_references_not_matched_by_helper_functions() -> None:
config = {
"conditions": [
{"condition": "template", "value_template": "{{ has_value('sensor.alpha') }}"},
{"condition": "template", "value_template": "{{ states.sensor.beta }}"},
{
"condition": "template",
"value_template": (
"{% if is_state('binary_sensor.door','on') "
"and has_value('sensor.temp') %}on{% endif %}"
),
},
],
"actions": [],
}

references = collect_entity_references(config, {"sensor", "binary_sensor"})

assert sorted(references) == [
"binary_sensor.door",
"sensor.alpha",
"sensor.beta",
"sensor.temp",
]