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
This commit is contained in:
J. Nick Koston 2020-07-14 19:34:35 -10:00 committed by GitHub
parent e938dcfbda
commit 44fefb3216
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 63 additions and 44 deletions

View file

@ -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

View file

@ -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
)

View file

@ -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
)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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
)

View file

@ -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)

View file

@ -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
)

View file

@ -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")

View file

@ -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")