Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update trigger based template entity resolution order #140660

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Open
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
21 changes: 16 additions & 5 deletions homeassistant/components/command_line/__init__.py
Original file line number Diff line number Diff line change
@@ -56,7 +56,10 @@
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.trigger_template_entity import CONF_AVAILABILITY
from homeassistant.helpers.trigger_template_entity import (
CONF_AVAILABILITY,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType

from .const import (
@@ -91,7 +94,9 @@
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(
@@ -108,7 +113,9 @@
vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ICON): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_UNIQUE_ID): cv.string,
@@ -134,7 +141,9 @@
vol.Optional(CONF_NAME, default=SENSOR_DEFAULT_NAME): cv.string,
vol.Optional(CONF_ICON): cv.template,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
@@ -150,7 +159,9 @@
vol.Optional(CONF_COMMAND_ON, default="true"): cv.string,
vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_ICON): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
19 changes: 13 additions & 6 deletions homeassistant/components/command_line/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity
from homeassistant.helpers.trigger_template_entity import (
ManualTriggerEntity,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util

@@ -50,7 +53,7 @@ async def async_setup_platform(
scan_interval: timedelta = binary_sensor_config.get(
CONF_SCAN_INTERVAL, SCAN_INTERVAL
)
value_template: Template | None = binary_sensor_config.get(CONF_VALUE_TEMPLATE)
value_template: ValueTemplate | None = binary_sensor_config.get(CONF_VALUE_TEMPLATE)

data = CommandSensorData(hass, command, command_timeout)

@@ -86,7 +89,7 @@ def __init__(
config: ConfigType,
payload_on: str,
payload_off: str,
value_template: Template | None,
value_template: ValueTemplate | None,
scan_interval: timedelta,
) -> None:
"""Initialize the Command line binary sensor."""
@@ -133,17 +136,21 @@ async def _async_update(self) -> None:
await self.data.async_update()
value = self.data.value

variables = self._template_variables_with_value(value)
if not self._render_availability_template(variables):
return

if self._value_template is not None:
value = self._value_template.async_render_with_possible_json_value(
value, None
value = self._value_template.async_render_as_value_template(
self.entity_id, variables, None
)
self._attr_is_on = None
if value == self._payload_on:
self._attr_is_on = True
elif value == self._payload_off:
self._attr_is_on = False

self._process_manual_data(value)
self._process_manual_data(variables)
self.async_write_ha_state()

async def async_update(self) -> None:
18 changes: 13 additions & 5 deletions homeassistant/components/command_line/cover.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity
from homeassistant.helpers.trigger_template_entity import (
ManualTriggerEntity,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util, slugify

@@ -79,7 +82,7 @@ def __init__(
command_close: str,
command_stop: str,
command_state: str | None,
value_template: Template | None,
value_template: ValueTemplate | None,
timeout: int,
scan_interval: timedelta,
) -> None:
@@ -164,14 +167,19 @@ async def _async_update(self) -> None:
"""Update device state."""
if self._command_state:
payload = str(await self._async_query_state())

variables = self._template_variables_with_value(payload)
if not self._render_availability_template(variables):
return

if self._value_template:
payload = self._value_template.async_render_with_possible_json_value(
payload, None
payload = self._value_template.async_render_as_value_template(
self.entity_id, variables, None
)
self._state = None
if payload:
self._state = int(payload)
self._process_manual_data(payload)
self._process_manual_data(variables)
self.async_write_ha_state()

async def async_update(self) -> None:
31 changes: 21 additions & 10 deletions homeassistant/components/command_line/sensor.py
Original file line number Diff line number Diff line change
@@ -23,7 +23,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerSensorEntity
from homeassistant.helpers.trigger_template_entity import (
ManualTriggerSensorEntity,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util

@@ -57,7 +60,7 @@ async def async_setup_platform(
json_attributes: list[str] | None = sensor_config.get(CONF_JSON_ATTRIBUTES)
json_attributes_path: str | None = sensor_config.get(CONF_JSON_ATTRIBUTES_PATH)
scan_interval: timedelta = sensor_config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
value_template: Template | None = sensor_config.get(CONF_VALUE_TEMPLATE)
value_template: ValueTemplate | None = sensor_config.get(CONF_VALUE_TEMPLATE)
data = CommandSensorData(hass, command, command_timeout)

trigger_entity_config = {
@@ -88,7 +91,7 @@ def __init__(
self,
data: CommandSensorData,
config: ConfigType,
value_template: Template | None,
value_template: ValueTemplate | None,
json_attributes: list[str] | None,
json_attributes_path: str | None,
scan_interval: timedelta,
@@ -145,7 +148,7 @@ async def _async_update(self) -> None:
value = self.data.value

if self._json_attributes:
self._attr_extra_state_attributes = {}
extra_state_attributes = {}
if value:
try:
json_dict = json.loads(value)
@@ -157,7 +160,7 @@ async def _async_update(self) -> None:
if isinstance(json_dict, list):
json_dict = json_dict[0]
if isinstance(json_dict, Mapping):
self._attr_extra_state_attributes = {
extra_state_attributes = {
k: json_dict[k]
for k in self._json_attributes
if k in json_dict
@@ -168,16 +171,24 @@ async def _async_update(self) -> None:
LOGGER.warning("Unable to parse output as JSON: %s", value)
else:
LOGGER.warning("Empty reply found when expecting JSON data")

if self._value_template is None:
self._attr_native_value = None
self._process_manual_data(value)
variables = self._template_variables_with_value(value)
if self._render_availability_template(variables):
self._attr_extra_state_attributes = extra_state_attributes
self._process_manual_data(variables)
return

self._attr_native_value = None
variables = self._template_variables_with_value(value)
if not self._render_availability_template(variables):
return

self._attr_extra_state_attributes = extra_state_attributes
if self._value_template is not None and value is not None:
value = self._value_template.async_render_with_possible_json_value(
value,
None,
value = self._value_template.async_render_as_value_template(
self.entity_id, variables, None
)

if self.device_class not in {
@@ -190,7 +201,7 @@ async def _async_update(self) -> None:
value, self.entity_id, self.device_class
)

self._process_manual_data(value)
self._process_manual_data(variables)
self.async_write_ha_state()

async def async_update(self) -> None:
18 changes: 13 additions & 5 deletions homeassistant/components/command_line/switch.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity
from homeassistant.helpers.trigger_template_entity import (
ManualTriggerEntity,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util, slugify

@@ -78,7 +81,7 @@ def __init__(
command_on: str,
command_off: str,
command_state: str | None,
value_template: Template | None,
value_template: ValueTemplate | None,
timeout: int,
scan_interval: timedelta,
) -> None:
@@ -166,15 +169,20 @@ async def _async_update(self) -> None:
"""Update device state."""
if self._command_state:
payload = str(await self._async_query_state())

variables = self._template_variables_with_value(payload)
if not self._render_availability_template(variables):
return

value = None
if self._value_template:
value = self._value_template.async_render_with_possible_json_value(
payload, None
value = self._value_template.async_render_as_value_template(
self.entity_id, variables, None
)
self._attr_is_on = None
if payload or value:
self._attr_is_on = (value or payload).lower() == "true"
self._process_manual_data(payload)
self._process_manual_data(variables)
self.async_write_ha_state()

async def async_update(self) -> None:
14 changes: 9 additions & 5 deletions homeassistant/components/rest/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
CONF_AVAILABILITY,
CONF_PICTURE,
ManualTriggerEntity,
ValueTemplate,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -132,7 +133,7 @@ def __init__(
config[CONF_FORCE_UPDATE],
)
self._previous_data = None
self._value_template: Template | None = config.get(CONF_VALUE_TEMPLATE)
self._value_template: ValueTemplate | None = config.get(CONF_VALUE_TEMPLATE)

@property
def available(self) -> bool:
@@ -156,11 +157,14 @@ def _update_from_rest_data(self) -> None:
)
return

raw_value = response
variables = self._template_variables_with_value(response)
if not self._render_availability_template(variables):
self.async_write_ha_state()
return

if response is not None and self._value_template is not None:
response = self._value_template.async_render_with_possible_json_value(
response, False
response = self._value_template.async_render_as_value_template(
self.entity_id, variables, False
)

try:
@@ -173,5 +177,5 @@ def _update_from_rest_data(self) -> None:
"yes": True,
}.get(str(response).lower(), False)

self._process_manual_data(raw_value)
self._process_manual_data(variables)
self.async_write_ha_state()
9 changes: 7 additions & 2 deletions homeassistant/components/rest/schema.py
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@
CONF_AVAILABILITY,
TEMPLATE_ENTITY_BASE_SCHEMA,
TEMPLATE_SENSOR_BASE_SCHEMA,
ValueTemplate,
)
from homeassistant.util.ssl import SSLCipherList

@@ -76,15 +77,19 @@
**TEMPLATE_SENSOR_BASE_SCHEMA.schema,
vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv,
vol.Optional(CONF_JSON_ATTRS_PATH): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_AVAILABILITY): cv.template,
}

BINARY_SENSOR_SCHEMA = {
**TEMPLATE_ENTITY_BASE_SCHEMA.schema,
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): vol.All(
cv.template, ValueTemplate.from_template
),
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_AVAILABILITY): cv.template,
}
Loading

Unchanged files with check annotations Beta

@pytest.mark.parametrize(
(
"entity_id",
"service",

Check failure on line 400 in tests/components/home_connect/test_switch.py

GitHub Actions / Run tests Python 3.13 (6)

test_program_switch_functionality[switch.dryer_program_cotton-LaundryCare.Dryer.Program.Cotton-on-Dryer] AssertionError: assert False + where False = is_state('switch.dryer_program_cotton', 'off') + where is_state = <homeassistant.core.StateMachine object at 0x7f1750daeca0>.is_state + where <homeassistant.core.StateMachine object at 0x7f1750daeca0> = <HomeAssistant RUNNING>.states
"mock_attr",
"exception_match",
),
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4

Check failure on line 228 in tests/components/rest/test_init.py

GitHub Actions / Run tests Python 3.13 (6)

test_setup_minimum_resource_template assert 2 == 4 + where 2 = len([<state binary_sensor.binary_sensor1=on; friendly_name=binary_sensor1 @ 2025-04-09T12:09:22.290001-07:00>, <state binary_sensor.binary_sensor2=off; friendly_name=binary_sensor2 @ 2025-04-09T12:09:22.296876-07:00>]) + where [<state binary_sensor.binary_sensor1=on; friendly_name=binary_sensor1 @ 2025-04-09T12:09:22.290001-07:00>, <state binary_sensor.binary_sensor2=off; friendly_name=binary_sensor2 @ 2025-04-09T12:09:22.296876-07:00>] = async_all() + where async_all = <homeassistant.core.StateMachine object at 0x7f173ad73d30>.async_all + where <homeassistant.core.StateMachine object at 0x7f173ad73d30> = <HomeAssistant RUNNING>.states
assert hass.states.get("sensor.sensor1").state == "1"
assert hass.states.get("sensor.sensor2").state == "2"