Async template (#3909)

* port binary_sensor/template

* port sensor/template

* port switch/template

* fix unittest

* fix

* use task instead yield on it

* fix unittest

* fix unittest v2

* fix invalid config

* fix lint

* fix unuset import
This commit is contained in:
Pascal Vizeli 2016-10-17 07:00:55 +02:00 committed by GitHub
parent 555e533f67
commit 1540bb1279
4 changed files with 81 additions and 67 deletions

View file

@ -17,8 +17,8 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup template binary sensors."""
sensors = []
@ -61,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not sensors:
_LOGGER.error('No sensors added')
return False
add_devices(sensors)
hass.loop.create_task(add_devices(sensors))
return True
@ -74,21 +74,22 @@ class BinarySensorTemplate(BinarySensorDevice):
value_template, entity_ids):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._template = value_template
self._state = None
self.update()
self._async_render()
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_bsensor_state_listener)
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@property
def name(self):
@ -112,7 +113,11 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state."""
"""Update the state from the template."""
self._async_render()
def _async_render(self):
"""Render the state from the template."""
try:
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:

View file

@ -15,8 +15,8 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
ATTR_ENTITY_ID, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the template sensors."""
sensors = []
@ -59,7 +59,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not sensors:
_LOGGER.error("No sensors added")
return False
add_devices(sensors)
hass.loop.create_task(add_devices(sensors))
return True
@ -71,21 +72,23 @@ class SensorTemplate(Entity):
state_template, entity_ids):
"""Initialize the sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self._name = friendly_name
self._unit_of_measurement = unit_of_measurement
self._template = state_template
self._state = None
self.update()
# update state
self._async_render()
@callback
def template_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_sensor_state_listener)
async_track_state_change(
hass, entity_ids, template_sensor_state_listener)
@property
def name(self):
@ -109,7 +112,11 @@ class SensorTemplate(Entity):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
"""Update the state from the template."""
self._async_render()
def _async_render(self):
"""Render the state from the template."""
try:
self._state = self._template.async_render()
except TemplateError as ex:

View file

@ -16,8 +16,8 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
ATTR_ENTITY_ID, CONF_SWITCHES)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.script import Script
import homeassistant.helpers.config_validation as cv
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Template switch."""
switches = []
@ -53,6 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
state_template.extract_entities())
state_template.hass = hass
switches.append(
SwitchTemplate(
hass,
@ -66,7 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not switches:
_LOGGER.error("No switches added")
return False
add_devices(switches)
hass.loop.create_task(add_devices(switches))
return True
@ -78,23 +81,23 @@ class SwitchTemplate(SwitchDevice):
on_action, off_action, entity_ids):
"""Initialize the Template switch."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self._name = friendly_name
self._template = state_template
state_template.hass = hass
self._on_script = Script(hass, on_action)
self._off_script = Script(hass, off_action)
self._state = False
self.update()
self._async_render()
@callback
def template_switch_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_switch_state_listener)
async_track_state_change(
hass, entity_ids, template_switch_state_listener)
@property
def name(self):
@ -127,6 +130,10 @@ class SwitchTemplate(SwitchDevice):
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
self._async_render()
def _async_render(self):
"""Render the state from the template."""
try:
state = self._template.async_render().lower()

View file

@ -4,10 +4,10 @@ from unittest import mock
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
import homeassistant.bootstrap as bootstrap
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor import template
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template as template_hlpr
from homeassistant.util.async import run_callback_threadsafe
from tests.common import get_test_home_assistant, assert_setup_component
@ -29,38 +29,27 @@ class TestBinarySensorTemplate(unittest.TestCase):
@mock.patch.object(template, 'BinarySensorTemplate')
def test_setup(self, mock_template):
""""Test the setup."""
tpl = template_hlpr.Template('{{ foo }}', self.hass)
config = PLATFORM_SCHEMA({
'platform': 'template',
'sensors': {
'test': {
'friendly_name': 'virtual thingy',
'value_template': tpl,
'sensor_class': 'motion',
'entity_id': 'test'
config = {
'binary_sensor': {
'platform': 'template',
'sensors': {
'test': {
'friendly_name': 'virtual thingy',
'value_template': '{{ foo }}',
'sensor_class': 'motion',
},
},
}
})
add_devices = mock.MagicMock()
result = template.setup_platform(self.hass, config, add_devices)
self.assertTrue(result)
self.assertEqual(mock_template.call_count, 1)
self.assertEqual(
mock_template.call_args,
mock.call(
self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test'
)
)
self.assertEqual(add_devices.call_count, 1)
self.assertEqual(
add_devices.call_args, mock.call([mock_template.return_value])
)
},
}
with assert_setup_component(1):
assert bootstrap.setup_component(
self.hass, 'binary_sensor', config)
def test_setup_no_sensors(self):
""""Test setup with no sensors."""
with assert_setup_component(0):
assert bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'template'
}
})
@ -68,8 +57,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_device(self):
""""Test the setup with invalid devices."""
with assert_setup_component(0):
assert bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'template',
'sensors': {
'foo bar': {},
@ -80,8 +69,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_sensor_class(self):
""""Test setup with invalid sensor class."""
with assert_setup_component(0):
assert bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'template',
'sensors': {
'test': {
@ -95,8 +84,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_missing_template(self):
""""Test setup with invalid and missing template."""
with assert_setup_component(0):
assert bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
'binary_sensor': {
'platform': 'template',
'sensors': {
'test': {
@ -108,9 +97,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_attributes(self):
""""Test the attributes."""
vs = template.BinarySensorTemplate(
vs = run_callback_threadsafe(
self.hass.loop, template.BinarySensorTemplate,
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
).result()
self.assertFalse(vs.should_poll)
self.assertEqual('motion', vs.sensor_class)
self.assertEqual('Parent', vs.name)
@ -126,9 +117,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_event(self):
""""Test the event."""
vs = template.BinarySensorTemplate(
vs = run_callback_threadsafe(
self.hass.loop, template.BinarySensorTemplate,
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
).result()
vs.update_ha_state()
self.hass.block_till_done()
@ -140,9 +133,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
@mock.patch('homeassistant.helpers.template.Template.render')
def test_update_template_error(self, mock_render):
""""Test the template update error."""
vs = template.BinarySensorTemplate(
vs = run_callback_threadsafe(
self.hass.loop, template.BinarySensorTemplate,
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
).result()
mock_render.side_effect = TemplateError('foo')
vs.update()
mock_render.side_effect = TemplateError(