Make this
variable available in template entities (#65201)
* feat: make this variable available in template entities This makes the variable `this` available in template entities. It will simplify the use of self-referencing template entities. Because, without this, we have to repeat the entity id every time. If we can solve this without explicitly spelling the entity id, code can be re-used much better. As a side-effect, this will allow to use `variables`-like patterns, where attributes can be used as variables to calculate subsequent attributes or state. Example: ```yaml template: sensor: - name: test state: "{{ this.attributes.test }}" # not: "{{ state_attr('sensor.test', 'test' }}" attributes: test: "{{ now() }}" ``` * expose entity_id instead of this * add test * Refactor to expose this variable * Tweak repr dunder Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
parent
9316909e60
commit
d20a620590
3 changed files with 81 additions and 10 deletions
|
@ -27,7 +27,11 @@ from homeassistant.helpers.event import (
|
|||
TrackTemplateResult,
|
||||
async_track_template_result,
|
||||
)
|
||||
from homeassistant.helpers.template import Template, result_as_boolean
|
||||
from homeassistant.helpers.template import (
|
||||
Template,
|
||||
TemplateStateFromEntityId,
|
||||
result_as_boolean,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
CONF_ATTRIBUTE_TEMPLATES,
|
||||
|
@ -368,8 +372,11 @@ class TemplateEntity(Entity):
|
|||
async def _async_template_startup(self, *_) -> None:
|
||||
template_var_tups: list[TrackTemplate] = []
|
||||
has_availability_template = False
|
||||
|
||||
values = {"this": TemplateStateFromEntityId(self.hass, self.entity_id)}
|
||||
|
||||
for template, attributes in self._template_attrs.items():
|
||||
template_var_tup = TrackTemplate(template, None)
|
||||
template_var_tup = TrackTemplate(template, values)
|
||||
is_availability_template = False
|
||||
for attribute in attributes:
|
||||
# pylint: disable-next=protected-access
|
||||
|
|
|
@ -719,22 +719,24 @@ class DomainStates:
|
|||
return f"<template DomainStates('{self._domain}')>"
|
||||
|
||||
|
||||
class TemplateState(State):
|
||||
class TemplateStateBase(State):
|
||||
"""Class to represent a state object in a template."""
|
||||
|
||||
__slots__ = ("_hass", "_state", "_collect")
|
||||
__slots__ = ("_hass", "_collect", "_entity_id")
|
||||
|
||||
_state: State
|
||||
|
||||
# Inheritance is done so functions that check against State keep working
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self, hass: HomeAssistant, state: State, collect: bool = True) -> None:
|
||||
def __init__(self, hass: HomeAssistant, collect: bool, entity_id: str) -> None:
|
||||
"""Initialize template state."""
|
||||
self._hass = hass
|
||||
self._state = state
|
||||
self._collect = collect
|
||||
self._entity_id = entity_id
|
||||
|
||||
def _collect_state(self) -> None:
|
||||
if self._collect and _RENDER_INFO in self._hass.data:
|
||||
self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id)
|
||||
self._hass.data[_RENDER_INFO].entities.add(self._entity_id)
|
||||
|
||||
# Jinja will try __getitem__ first and it avoids the need
|
||||
# to call is_safe_attribute
|
||||
|
@ -743,10 +745,10 @@ class TemplateState(State):
|
|||
if item in _COLLECTABLE_STATE_ATTRIBUTES:
|
||||
# _collect_state inlined here for performance
|
||||
if self._collect and _RENDER_INFO in self._hass.data:
|
||||
self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id)
|
||||
self._hass.data[_RENDER_INFO].entities.add(self._entity_id)
|
||||
return getattr(self._state, item)
|
||||
if item == "entity_id":
|
||||
return self._state.entity_id
|
||||
return self._entity_id
|
||||
if item == "state_with_unit":
|
||||
return self.state_with_unit
|
||||
raise KeyError
|
||||
|
@ -757,7 +759,7 @@ class TemplateState(State):
|
|||
|
||||
Intentionally does not collect state
|
||||
"""
|
||||
return self._state.entity_id
|
||||
return self._entity_id
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -819,11 +821,44 @@ class TemplateState(State):
|
|||
self._collect_state()
|
||||
return self._state.__eq__(other)
|
||||
|
||||
|
||||
class TemplateState(TemplateStateBase):
|
||||
"""Class to represent a state object in a template."""
|
||||
|
||||
__slots__ = ("_state",)
|
||||
|
||||
# Inheritance is done so functions that check against State keep working
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self, hass: HomeAssistant, state: State, collect: bool = True) -> None:
|
||||
"""Initialize template state."""
|
||||
super().__init__(hass, collect, state.entity_id)
|
||||
self._state = state
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Representation of Template State."""
|
||||
return f"<template TemplateState({self._state!r})>"
|
||||
|
||||
|
||||
class TemplateStateFromEntityId(TemplateStateBase):
|
||||
"""Class to represent a state object in a template."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entity_id: str, collect: bool = True
|
||||
) -> None:
|
||||
"""Initialize template state."""
|
||||
super().__init__(hass, collect, entity_id)
|
||||
|
||||
@property
|
||||
def _state(self) -> State: # type: ignore[override] # mypy issue 4125
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
assert state
|
||||
return state
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Representation of Template State."""
|
||||
return f"<template TemplateStateFromEntityId({self._entity_id})>"
|
||||
|
||||
|
||||
def _collect_state(hass: HomeAssistant, entity_id: str) -> None:
|
||||
if (entity_collect := hass.data.get(_RENDER_INFO)) is not None:
|
||||
entity_collect.entities.add(entity_id)
|
||||
|
|
|
@ -620,6 +620,35 @@ async def test_sun_renders_once_per_sensor(hass, start_ha):
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
{
|
||||
"sensor": {
|
||||
"platform": "template",
|
||||
"sensors": {
|
||||
"test_template_sensor": {
|
||||
"value_template": "{{ this.attributes.test }}: {{ this.entity_id }}",
|
||||
"attribute_templates": {
|
||||
"test": "It {{ states.sensor.test_state.state }}"
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_this_variable(hass, start_ha):
|
||||
"""Test template."""
|
||||
assert hass.states.get(TEST_NAME).state == "It: " + TEST_NAME
|
||||
|
||||
hass.states.async_set("sensor.test_state", "Works")
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(TEST_NAME).state == "It Works: " + TEST_NAME
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue