Skip to content

Commit 978034b

Browse files
authored
Ignore entity ID prefixes in templates (#1306)
1 parent 29f41cf commit 978034b

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

custom_components/spook/entity_filtering.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,33 @@ def _is_concatenated_template_match(template_str: str, match: re.Match[str]) ->
418418
return before_literal.endswith("~") or after_literal.startswith("~")
419419

420420

421+
def _is_string_method_argument_match(template_str: str, match: re.Match[str]) -> bool:
422+
"""Return if an entity-like literal is used as a string method argument."""
423+
groups = match.groups()
424+
if len(groups) == _STATES_DOMAIN_ENTITY_GROUPS:
425+
return False
426+
427+
entity_start = match.span(1)[0]
428+
before_entity = template_str[:entity_start].rstrip()
429+
if not before_entity.endswith(("'", '"')):
430+
return False
431+
432+
before_literal = before_entity[:-1].rstrip()
433+
for method in (".startswith", ".endswith"):
434+
if method not in before_literal:
435+
continue
436+
437+
after_method = before_literal.rsplit(method, maxsplit=1)[1].lstrip()
438+
if not after_method.startswith("("):
439+
continue
440+
441+
between_call_and_argument = after_method[1:].strip()
442+
if not between_call_and_argument or set(between_call_and_argument) == {"("}:
443+
return True
444+
445+
return False
446+
447+
421448
def _entity_id_from_template_match(match: re.Match[str]) -> str:
422449
"""Return the entity ID captured by a template regex match."""
423450
groups = match.groups()
@@ -446,7 +473,9 @@ def extract_entities_from_template_regex(
446473

447474
for pattern in ENTITY_ID_TEMPLATE_PATTERNS:
448475
for match in re.finditer(pattern, template_str, re.IGNORECASE):
449-
if _is_concatenated_template_match(template_str, match):
476+
if _is_concatenated_template_match(
477+
template_str, match
478+
) or _is_string_method_argument_match(template_str, match):
450479
continue
451480

452481
entity_id = _entity_id_from_template_match(match)

tests/ectoplasms/automation/repairs/test_unknown_entity_references.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,38 @@ async def test_value_template_ignores_concatenated_entity_id_literal(
6666
assert await extract_entities_from_value(hass, template) == set()
6767

6868

69+
async def test_value_template_ignores_entity_id_prefix_string_match(
70+
hass: HomeAssistant,
71+
) -> None:
72+
"""String prefix checks are not complete entity references."""
73+
template = (
74+
"{% for entity in states.binary_sensor if "
75+
"entity.entity_id.startswith('binary_sensor.proxmox') %}"
76+
"{{ entity.state }}"
77+
"{% endfor %}"
78+
)
79+
80+
assert await extract_entities_from_value(hass, template) == set()
81+
82+
83+
async def test_value_template_ignores_grouped_entity_id_prefix_string_match(
84+
hass: HomeAssistant,
85+
) -> None:
86+
"""String prefix checks can use grouping without becoming references."""
87+
template = "{{ entity.entity_id.startswith( ('binary_sensor.proxmox')) }}"
88+
89+
assert await extract_entities_from_value(hass, template) == set()
90+
91+
92+
async def test_value_template_ignores_entity_id_suffix_string_match(
93+
hass: HomeAssistant,
94+
) -> None:
95+
"""String suffix checks are not complete entity references."""
96+
template = "{{ entity.entity_id.endswith('sensor.power') }}"
97+
98+
assert await extract_entities_from_value(hass, template) == set()
99+
100+
69101
async def test_value_template_ignores_concatenated_helper_entity_id(
70102
hass: HomeAssistant,
71103
) -> None:

0 commit comments

Comments
 (0)