From 44fefb3216e0cca11f718e5cbc25870bec58fc9c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 14 Jul 2020 19:34:35 -1000 Subject: [PATCH] Improve handling of template platforms when entity extraction fails (#37831) Most of the the template platforms would check for extract_entities failing to extract entities and avoid setting up a state change listner for MATCH_ALL after extract_entities had warned that it could not extract the entities and updates would need to be done manually. This protection has been extended to all template platforms. Alter the behavior of extract_entities to return the successfully extracted entities if one or more templates fail extraction instead of returning MATCH_ALL --- homeassistant/components/template/__init__.py | 7 ++++--- .../components/template/alarm_control_panel.py | 9 +++++---- homeassistant/components/template/binary_sensor.py | 9 ++++++--- homeassistant/components/template/cover.py | 13 ++++++++----- homeassistant/components/template/fan.py | 11 +++++++---- homeassistant/components/template/light.py | 13 ++++++++----- homeassistant/components/template/lock.py | 6 +++--- homeassistant/components/template/sensor.py | 6 +++--- homeassistant/components/template/switch.py | 13 ++++++++----- homeassistant/components/template/vacuum.py | 4 ++-- tests/components/template/test_binary_sensor.py | 7 ++++--- tests/components/template/test_sensor.py | 9 +++++---- 12 files changed, 63 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 04808970af2..de9d60feceb 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -40,8 +40,11 @@ def extract_entities( else: invalid_templates.append(template_name.replace("_template", "")) + entity_ids = list(entity_ids) + if invalid_templates: - entity_ids = MATCH_ALL + if not entity_ids: + entity_ids = MATCH_ALL _LOGGER.warning( "Template %s '%s' has no entity ids configured to track nor" " were we able to extract the entities to track from the %s " @@ -51,8 +54,6 @@ def extract_entities( device_name, ", ".join(invalid_templates), ) - else: - entity_ids = list(entity_ids) else: entity_ids = manual_entity_ids diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index 3d6ac1dbe0e..b7ad219eff7 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -32,7 +32,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.script import Script _LOGGER = logging.getLogger(__name__) @@ -204,15 +204,16 @@ class AlarmControlPanelTemplate(AlarmControlPanelEntity): """Register callbacks.""" @callback - def template_alarm_state_listener(entity, old_state, new_state): + def template_alarm_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_alarm_control_panel_startup(event): """Update template on startup.""" - if self._template is not None: - async_track_state_change( + if self._template is not None and self._entities != MATCH_ALL: + # Track state change only for valid templates + async_track_state_change_event( self.hass, self._entities, template_alarm_state_listener ) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 91ffba6dd25..101651fabd5 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -24,7 +24,10 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_same_state, async_track_state_change +from homeassistant.helpers.event import ( + async_track_same_state, + async_track_state_change_event, +) from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE @@ -148,7 +151,7 @@ class BinarySensorTemplate(BinarySensorEntity): """Register callbacks.""" @callback - def template_bsensor_state_listener(entity, old_state, new_state): + def template_bsensor_state_listener(event): """Handle the target device state changes.""" self.async_check_state() @@ -157,7 +160,7 @@ class BinarySensorTemplate(BinarySensorEntity): """Update template on startup.""" if self._entities != MATCH_ALL: # Track state change only for valid templates - async_track_state_change( + async_track_state_change_event( self.hass, self._entities, template_bsensor_state_listener ) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 16140de67d8..e2f67acf2bd 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -28,6 +28,7 @@ from homeassistant.const import ( CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, + MATCH_ALL, STATE_CLOSED, STATE_OPEN, ) @@ -35,7 +36,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.script import Script from . import extract_entities, initialise_templates @@ -226,16 +227,18 @@ class CoverTemplate(CoverEntity): """Register callbacks.""" @callback - def template_cover_state_listener(entity, old_state, new_state): + def template_cover_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_cover_startup(event): """Update template on startup.""" - async_track_state_change( - self.hass, self._entities, template_cover_state_listener - ) + if self._entities != MATCH_ALL: + # Track state change only for valid templates + async_track_state_change_event( + self.hass, self._entities, template_cover_state_listener + ) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index ecdb4ec7eec..037cc6c40e1 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -23,6 +23,7 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, + MATCH_ALL, STATE_OFF, STATE_ON, STATE_UNKNOWN, @@ -313,16 +314,18 @@ class TemplateFan(FanEntity): """Register callbacks.""" @callback - def template_fan_state_listener(entity, old_state, new_state): + def template_fan_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_fan_startup(event): """Update template on startup.""" - self.hass.helpers.event.async_track_state_change( - self._entities, template_fan_state_listener - ) + if self._entities != MATCH_ALL: + # Track state change only for valid templates + self.hass.helpers.event.async_track_state_change_event( + self._entities, template_fan_state_listener + ) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index d96c6f62dce..6832ca04017 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -23,6 +23,7 @@ from homeassistant.const import ( CONF_LIGHTS, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, + MATCH_ALL, STATE_OFF, STATE_ON, ) @@ -31,7 +32,7 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.script import Script from . import extract_entities, initialise_templates @@ -277,7 +278,7 @@ class LightTemplate(LightEntity): """Register callbacks.""" @callback - def template_light_state_listener(entity, old_state, new_state): + def template_light_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @@ -292,9 +293,11 @@ class LightTemplate(LightEntity): or self._white_value_template is not None or self._availability_template is not None ): - async_track_state_change( - self.hass, self._entities, template_light_state_listener - ) + if self._entities != MATCH_ALL: + # Track state change only for valid templates + async_track_state_change_event( + self.hass, self._entities, template_light_state_listener + ) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 7a50e34f8cb..0d8cdd7d290 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -16,7 +16,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.script import Script from . import extract_entities, initialise_templates @@ -102,7 +102,7 @@ class TemplateLock(LockEntity): """Register callbacks.""" @callback - def template_lock_state_listener(entity, old_state, new_state): + def template_lock_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @@ -111,7 +111,7 @@ class TemplateLock(LockEntity): """Update template on startup.""" if self._state_entities != MATCH_ALL: # Track state change only for valid templates - async_track_state_change( + async_track_state_change_event( self._hass, self._state_entities, template_lock_state_listener ) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 44f83699097..d4977d626ca 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -26,7 +26,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE @@ -154,7 +154,7 @@ class SensorTemplate(Entity): """Register callbacks.""" @callback - def template_sensor_state_listener(entity, old_state, new_state): + def template_sensor_state_listener(event): """Handle device state changes.""" self.async_schedule_update_ha_state(True) @@ -163,7 +163,7 @@ class SensorTemplate(Entity): """Update template on startup.""" if self._entities != MATCH_ALL: # Track state change only for valid templates - async_track_state_change( + async_track_state_change_event( self.hass, self._entities, template_sensor_state_listener ) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index abc9a432529..124d12d194f 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -16,6 +16,7 @@ from homeassistant.const import ( CONF_SWITCHES, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, + MATCH_ALL, STATE_OFF, STATE_ON, ) @@ -23,7 +24,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import Script @@ -147,16 +148,18 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): # set up event listening @callback - def template_switch_state_listener(entity, old_state, new_state): + def template_switch_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_switch_startup(event): """Update template on startup.""" - async_track_state_change( - self.hass, self._entities, template_switch_state_listener - ) + if self._entities != MATCH_ALL: + # Track state change only for valid templates + async_track_state_change_event( + self.hass, self._entities, template_switch_state_listener + ) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 040b4324b05..1209e617a7e 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -341,7 +341,7 @@ class TemplateVacuum(StateVacuumEntity): """Register callbacks.""" @callback - def template_vacuum_state_listener(entity, old_state, new_state): + def template_vacuum_state_listener(event): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @@ -350,7 +350,7 @@ class TemplateVacuum(StateVacuumEntity): """Update template on startup.""" if self._entities != MATCH_ALL: # Track state changes only for valid templates - self.hass.helpers.event.async_track_state_change( + self.hass.helpers.event.async_track_state_change_event( self._entities, template_vacuum_state_listener ) diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 698d1c2ca80..482a72082cd 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -622,9 +622,10 @@ async def test_no_update_template_match_all(hass, caplog): await hass.async_block_till_done() assert hass.states.get("binary_sensor.all_state").state == "on" - assert hass.states.get("binary_sensor.all_icon").state == "on" - assert hass.states.get("binary_sensor.all_entity_picture").state == "on" - assert hass.states.get("binary_sensor.all_attribute").state == "on" + # Will now process because we have one valid template + assert hass.states.get("binary_sensor.all_icon").state == "off" + assert hass.states.get("binary_sensor.all_entity_picture").state == "off" + assert hass.states.get("binary_sensor.all_attribute").state == "off" await hass.helpers.entity_component.async_update_entity("binary_sensor.all_state") await hass.helpers.entity_component.async_update_entity("binary_sensor.all_icon") diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 0fbce50f2a3..d61b1be4a7f 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -618,10 +618,11 @@ async def test_no_template_match_all(hass, caplog): await hass.async_block_till_done() assert hass.states.get("sensor.invalid_state").state == "2" - assert hass.states.get("sensor.invalid_icon").state == "startup" - assert hass.states.get("sensor.invalid_entity_picture").state == "startup" - assert hass.states.get("sensor.invalid_friendly_name").state == "startup" - assert hass.states.get("sensor.invalid_attribute").state == "startup" + # Will now process because we have at least one valid template + assert hass.states.get("sensor.invalid_icon").state == "hello" + assert hass.states.get("sensor.invalid_entity_picture").state == "hello" + assert hass.states.get("sensor.invalid_friendly_name").state == "hello" + assert hass.states.get("sensor.invalid_attribute").state == "hello" await hass.helpers.entity_component.async_update_entity("sensor.invalid_state") await hass.helpers.entity_component.async_update_entity("sensor.invalid_icon")