Pre-compile templates (#3515)

* Pre-compile templates

* Compile templates in numeric_state condition
This commit is contained in:
Paulus Schoutsen 2016-09-25 13:33:01 -07:00 committed by GitHub
parent b3d67a7ed9
commit 0c0feda834
15 changed files with 134 additions and 80 deletions

View file

@ -12,7 +12,7 @@ from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE) CONF_BELOW, CONF_ABOVE)
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers import condition, config_validation as cv, template
TRIGGER_SCHEMA = vol.All(vol.Schema({ TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'numeric_state', vol.Required(CONF_PLATFORM): 'numeric_state',
@ -31,6 +31,8 @@ def trigger(hass, config, action):
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE) above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s): def state_automation_listener(entity, from_s, to_s):

View file

@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL) CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
from homeassistant.helpers import condition from homeassistant.helpers import condition, template
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -25,7 +25,8 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
def trigger(hass, config, action): def trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = template.compile_template(
hass, config.get(CONF_VALUE_TEMPLATE))
# Local variable to keep track of if the action has already been triggered # Local variable to keep track of if the action has already been triggered
already_triggered = False already_triggered = False

View file

@ -46,6 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_class = config.get(CONF_SENSOR_CLASS) sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
data = CommandSensorData(command) data = CommandSensorData(command)
add_devices([CommandBinarySensor( add_devices([CommandBinarySensor(

View file

@ -37,6 +37,11 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor.""" """Setup the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
add_devices([MqttBinarySensor( add_devices([MqttBinarySensor(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
@ -45,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_QOS), config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF), config.get(CONF_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE) value_template
)]) )])

View file

@ -45,6 +45,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_class = config.get(CONF_SENSOR_CLASS) sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
rest = RestData(method, resource, payload, verify_ssl) rest = RestData(method, resource, payload, verify_ssl)
rest.update() rest.update()

View file

@ -73,7 +73,7 @@ class BinarySensorTemplate(BinarySensorDevice):
hass=hass) hass=hass)
self._name = friendly_name self._name = friendly_name
self._sensor_class = sensor_class self._sensor_class = sensor_class
self._template = value_template self._template = template.compile_template(hass, value_template)
self._state = None self._state = None
self.update() self.update()

View file

@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the template sensors.""" """Setup the trend sensors."""
sensors = [] sensors = []
for device, device_config in config[CONF_SENSORS].items(): for device, device_config in config[CONF_SENSORS].items():

View file

@ -38,18 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a generic IP Camera.""" """Setup a generic IP Camera."""
add_devices([GenericCamera(config)]) add_devices([GenericCamera(hass, config)])
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class GenericCamera(Camera): class GenericCamera(Camera):
"""A generic implementation of an IP camera.""" """A generic implementation of an IP camera."""
def __init__(self, device_info): def __init__(self, hass, device_info):
"""Initialize a generic camera.""" """Initialize a generic camera."""
super().__init__() super().__init__()
self.hass = hass
self._name = device_info.get(CONF_NAME) self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL] self._still_image_url = template.compile_template(
hass, device_info[CONF_STILL_IMAGE_URL])
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME) username = device_info.get(CONF_USERNAME)

View file

@ -24,7 +24,7 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_COMMAND_STATE): cv.string, vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string, vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string,
vol.Optional(CONF_FRIENDLY_NAME): cv.string, vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE, default='{{ value }}'): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}) })
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -38,6 +38,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
covers = [] covers = []
for device_name, device_config in devices.items(): for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
covers.append( covers.append(
CommandCover( CommandCover(
hass, hass,
@ -46,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device_config.get(CONF_COMMAND_CLOSE), device_config.get(CONF_COMMAND_CLOSE),
device_config.get(CONF_COMMAND_STOP), device_config.get(CONF_COMMAND_STOP),
device_config.get(CONF_COMMAND_STATE), device_config.get(CONF_COMMAND_STATE),
device_config.get(CONF_VALUE_TEMPLATE), value_template,
) )
) )

View file

@ -44,11 +44,17 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT Cover.""" """Setup the MQTT Cover."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = template.compile_template(hass, value_template)
add_devices([MqttCover( add_devices([MqttCover(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
@ -62,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_CLOSE), config.get(CONF_PAYLOAD_CLOSE),
config.get(CONF_PAYLOAD_STOP), config.get(CONF_PAYLOAD_STOP),
config.get(CONF_OPTIMISTIC), config.get(CONF_OPTIMISTIC),
config.get(CONF_VALUE_TEMPLATE) value_template,
)]) )])

View file

@ -71,7 +71,7 @@ class SensorTemplate(Entity):
hass=hass) hass=hass)
self._name = friendly_name self._name = friendly_name
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._template = state_template self._template = template.compile_template(hass, state_template)
self._state = None self._state = None
self.update() self.update()

View file

@ -79,7 +79,7 @@ class SwitchTemplate(SwitchDevice):
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass) hass=hass)
self._name = friendly_name self._name = friendly_name
self._template = state_template self._template = template.compile_template(hass, state_template)
self._on_script = Script(hass, on_action) self._on_script = Script(hass, on_action)
self._off_script = Script(hass, off_action) self._off_script = Script(hass, off_action)
self._state = False self._state = False

View file

@ -16,7 +16,7 @@ from homeassistant.const import (
CONF_BELOW, CONF_ABOVE) CONF_BELOW, CONF_ABOVE)
from homeassistant.exceptions import TemplateError, HomeAssistantError from homeassistant.exceptions import TemplateError, HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render from homeassistant.helpers.template import render, compile_template
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
FROM_CONFIG_FORMAT = '{}_from_config' FROM_CONFIG_FORMAT = '{}_from_config'
@ -125,9 +125,18 @@ def numeric_state_from_config(config, config_validation=True):
above = config.get(CONF_ABOVE) above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
cache = {}
def if_numeric_state(hass, variables=None): def if_numeric_state(hass, variables=None):
"""Test numeric state condition.""" """Test numeric state condition."""
return numeric_state(hass, entity_id, below, above, value_template, if value_template is None:
tmpl = None
elif hass in cache:
tmpl = cache[hass]
else:
cache[hass] = tmpl = compile_template(hass, value_template)
return numeric_state(hass, entity_id, below, above, tmpl,
variables) variables)
return if_numeric_state return if_numeric_state
@ -222,9 +231,16 @@ def template_from_config(config, config_validation=True):
config = cv.TEMPLATE_CONDITION_SCHEMA(config) config = cv.TEMPLATE_CONDITION_SCHEMA(config)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
cache = {}
def template_if(hass, variables=None): def template_if(hass, variables=None):
"""Validate template based if-condition.""" """Validate template based if-condition."""
return template(hass, value_template, variables) if hass in cache:
tmpl = cache[hass]
else:
cache[hass] = tmpl = compile_template(hass, value_template)
return template(hass, tmpl, variables)
return template_if return template_if

View file

@ -18,6 +18,24 @@ _SENTINEL = object()
DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S" DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
def compile_template(hass, template):
"""Compile a template."""
location_methods = LocationMethods(hass)
return ENV.from_string(template, {
'closest': location_methods.closest,
'distance': location_methods.distance,
'float': forgiving_float,
'is_state': hass.states.is_state,
'is_state_attr': hass.states.is_state_attr,
'now': dt_util.now,
'states': AllStates(hass),
'utcnow': dt_util.utcnow,
'as_timestamp': dt_util.as_timestamp,
'relative_time': dt_util.get_age
})
def render_with_possible_json_value(hass, template, value, def render_with_possible_json_value(hass, template, value,
error_value=_SENTINEL): error_value=_SENTINEL):
"""Render template with value exposed. """Render template with value exposed.
@ -44,22 +62,11 @@ def render(hass, template, variables=None, **kwargs):
if variables is not None: if variables is not None:
kwargs.update(variables) kwargs.update(variables)
location_methods = LocationMethods(hass)
utcnow = dt_util.utcnow()
try: try:
return ENV.from_string(template, { if not isinstance(template, jinja2.Template):
'closest': location_methods.closest, template = compile_template(hass, template)
'distance': location_methods.distance,
'float': forgiving_float, return template.render(kwargs).strip()
'is_state': hass.states.is_state,
'is_state_attr': hass.states.is_state_attr,
'now': dt_util.as_local(utcnow),
'states': AllStates(hass),
'utcnow': utcnow,
'as_timestamp': dt_util.as_timestamp,
'relative_time': dt_util.get_age
}).render(kwargs).strip()
except jinja2.TemplateError as err: except jinja2.TemplateError as err:
raise TemplateError(err) raise TemplateError(err)

View file

@ -33,14 +33,14 @@ class TestUtilTemplate(unittest.TestCase):
self.hass.stop() self.hass.stop()
def test_referring_states_by_entity_id(self): def test_referring_states_by_entity_id(self):
""".""" """Test referring states by entity id."""
self.hass.states.set('test.object', 'happy') self.hass.states.set('test.object', 'happy')
self.assertEqual( self.assertEqual(
'happy', 'happy',
template.render(self.hass, '{{ states.test.object.state }}')) template.render(self.hass, '{{ states.test.object.state }}'))
def test_iterating_all_states(self): def test_iterating_all_states(self):
""".""" """Test iterating all states."""
self.hass.states.set('test.object', 'happy') self.hass.states.set('test.object', 'happy')
self.hass.states.set('sensor.temperature', 10) self.hass.states.set('sensor.temperature', 10)
@ -51,7 +51,7 @@ class TestUtilTemplate(unittest.TestCase):
'{% for state in states %}{{ state.state }}{% endfor %}')) '{% for state in states %}{{ state.state }}{% endfor %}'))
def test_iterating_domain_states(self): def test_iterating_domain_states(self):
""".""" """Test iterating domain states."""
self.hass.states.set('test.object', 'happy') self.hass.states.set('test.object', 'happy')
self.hass.states.set('sensor.back_door', 'open') self.hass.states.set('sensor.back_door', 'open')
self.hass.states.set('sensor.temperature', 10) self.hass.states.set('sensor.temperature', 10)
@ -65,7 +65,7 @@ class TestUtilTemplate(unittest.TestCase):
""")) """))
def test_float(self): def test_float(self):
""".""" """Test float."""
self.hass.states.set('sensor.temperature', '12') self.hass.states.set('sensor.temperature', '12')
self.assertEqual( self.assertEqual(
@ -81,7 +81,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ float(states.sensor.temperature.state) > 11 }}')) '{{ float(states.sensor.temperature.state) > 11 }}'))
def test_rounding_value(self): def test_rounding_value(self):
""".""" """Test rounding value."""
self.hass.states.set('sensor.temperature', 12.78) self.hass.states.set('sensor.temperature', 12.78)
self.assertEqual( self.assertEqual(
@ -98,7 +98,7 @@ class TestUtilTemplate(unittest.TestCase):
)) ))
def test_rounding_value_get_original_value_on_error(self): def test_rounding_value_get_original_value_on_error(self):
""".""" """Test rounding value get original value on error."""
self.assertEqual( self.assertEqual(
'None', 'None',
template.render( template.render(
@ -114,7 +114,7 @@ class TestUtilTemplate(unittest.TestCase):
)) ))
def test_multiply(self): def test_multiply(self):
""".""" """Test multiply."""
tests = { tests = {
None: 'None', None: 'None',
10: '100', 10: '100',
@ -180,50 +180,50 @@ class TestUtilTemplate(unittest.TestCase):
'{{ %s | timestamp_utc }}' % inp)) '{{ %s | timestamp_utc }}' % inp))
def test_passing_vars_as_keywords(self): def test_passing_vars_as_keywords(self):
""".""" """Test passing variables as keywords."""
self.assertEqual( self.assertEqual(
'127', template.render(self.hass, '{{ hello }}', hello=127)) '127', template.render(self.hass, '{{ hello }}', hello=127))
def test_passing_vars_as_vars(self): def test_passing_vars_as_vars(self):
""".""" """Test passing variables as variables."""
self.assertEqual( self.assertEqual(
'127', template.render(self.hass, '{{ hello }}', {'hello': 127})) '127', template.render(self.hass, '{{ hello }}', {'hello': 127}))
def test_render_with_possible_json_value_with_valid_json(self): def test_render_with_possible_json_value_with_valid_json(self):
""".""" """Render with possible JSON value with valid JSON."""
self.assertEqual( self.assertEqual(
'world', 'world',
template.render_with_possible_json_value( template.render_with_possible_json_value(
self.hass, '{{ value_json.hello }}', '{"hello": "world"}')) self.hass, '{{ value_json.hello }}', '{"hello": "world"}'))
def test_render_with_possible_json_value_with_invalid_json(self): def test_render_with_possible_json_value_with_invalid_json(self):
""".""" """Render with possible JSON value with invalid JSON."""
self.assertEqual( self.assertEqual(
'', '',
template.render_with_possible_json_value( template.render_with_possible_json_value(
self.hass, '{{ value_json }}', '{ I AM NOT JSON }')) self.hass, '{{ value_json }}', '{ I AM NOT JSON }'))
def test_render_with_possible_json_value_with_template_error(self): def test_render_with_possible_json_value_with_template_error(self):
""".""" """Render with possible JSON value with template error."""
self.assertEqual( self.assertEqual(
'hello', 'hello',
template.render_with_possible_json_value( template.render_with_possible_json_value(
self.hass, '{{ value_json', 'hello')) self.hass, '{{ value_json', 'hello'))
def test_render_with_possible_json_value_with_template_error_value(self): def test_render_with_possible_json_value_with_template_error_value(self):
""".""" """Render with possible JSON value with template error value."""
self.assertEqual( self.assertEqual(
'-', '-',
template.render_with_possible_json_value( template.render_with_possible_json_value(
self.hass, '{{ value_json', 'hello', '-')) self.hass, '{{ value_json', 'hello', '-'))
def test_raise_exception_on_error(self): def test_raise_exception_on_error(self):
""".""" """Test raising an exception on error."""
with self.assertRaises(TemplateError): with self.assertRaises(TemplateError):
template.render(self.hass, '{{ invalid_syntax') template.render(self.hass, '{{ invalid_syntax')
def test_if_state_exists(self): def test_if_state_exists(self):
""".""" """Test if state exists works."""
self.hass.states.set('test.object', 'available') self.hass.states.set('test.object', 'available')
self.assertEqual( self.assertEqual(
'exists', 'exists',
@ -234,7 +234,7 @@ class TestUtilTemplate(unittest.TestCase):
""")) """))
def test_is_state(self): def test_is_state(self):
""".""" """Test is_state method."""
self.hass.states.set('test.object', 'available') self.hass.states.set('test.object', 'available')
self.assertEqual( self.assertEqual(
'yes', 'yes',
@ -245,7 +245,7 @@ class TestUtilTemplate(unittest.TestCase):
""")) """))
def test_is_state_attr(self): def test_is_state_attr(self):
""".""" """Test is_state_attr method."""
self.hass.states.set('test.object', 'available', {'mode': 'on'}) self.hass.states.set('test.object', 'available', {'mode': 'on'})
self.assertEqual( self.assertEqual(
'yes', 'yes',
@ -256,7 +256,7 @@ class TestUtilTemplate(unittest.TestCase):
""")) """))
def test_states_function(self): def test_states_function(self):
""".""" """Test using states as a function."""
self.hass.states.set('test.object', 'available') self.hass.states.set('test.object', 'available')
self.assertEqual( self.assertEqual(
'available', 'available',
@ -265,32 +265,26 @@ class TestUtilTemplate(unittest.TestCase):
'unknown', 'unknown',
template.render(self.hass, '{{ states("test.object2") }}')) template.render(self.hass, '{{ states("test.object2") }}'))
@patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) @patch('homeassistant.core.dt_util.now', return_value=dt_util.now())
@patch('homeassistant.helpers.template.TemplateEnvironment.' @patch('homeassistant.helpers.template.TemplateEnvironment.'
'is_safe_callable', return_value=True) 'is_safe_callable', return_value=True)
def test_now(self, mock_is_safe, mock_utcnow): def test_now(self, mock_is_safe, mock_utcnow):
""".""" """Test now method."""
self.assertEqual( self.assertEqual(
dt_util.utcnow().isoformat(), dt_util.now().isoformat(),
template.render(self.hass, '{{ now.isoformat() }}')) template.render(self.hass, '{{ now().isoformat() }}'))
@patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow())
@patch('homeassistant.helpers.template.TemplateEnvironment.' @patch('homeassistant.helpers.template.TemplateEnvironment.'
'is_safe_callable', return_value=True) 'is_safe_callable', return_value=True)
def test_utcnow(self, mock_is_safe, mock_utcnow): def test_utcnow(self, mock_is_safe, mock_utcnow):
""".""" """Test utcnow method."""
self.assertEqual( self.assertEqual(
dt_util.utcnow().isoformat(), dt_util.utcnow().isoformat(),
template.render(self.hass, '{{ utcnow.isoformat() }}')) template.render(self.hass, '{{ utcnow().isoformat() }}'))
def test_utcnow_is_exactly_now(self):
"""."""
self.assertEqual(
'True',
template.render(self.hass, '{{ utcnow == now }}'))
def test_distance_function_with_1_state(self): def test_distance_function_with_1_state(self):
""".""" """Test distance function with 1 state."""
self.hass.states.set('test.object', 'happy', { self.hass.states.set('test.object', 'happy', {
'latitude': 32.87336, 'latitude': 32.87336,
'longitude': -117.22943, 'longitude': -117.22943,
@ -302,7 +296,7 @@ class TestUtilTemplate(unittest.TestCase):
self.hass, '{{ distance(states.test.object) | round }}')) self.hass, '{{ distance(states.test.object) | round }}'))
def test_distance_function_with_2_states(self): def test_distance_function_with_2_states(self):
""".""" """Test distance function with 2 states."""
self.hass.states.set('test.object', 'happy', { self.hass.states.set('test.object', 'happy', {
'latitude': 32.87336, 'latitude': 32.87336,
'longitude': -117.22943, 'longitude': -117.22943,
@ -321,14 +315,14 @@ class TestUtilTemplate(unittest.TestCase):
'| round }}')) '| round }}'))
def test_distance_function_with_1_coord(self): def test_distance_function_with_1_coord(self):
""".""" """Test distance function with 1 coord."""
self.assertEqual( self.assertEqual(
'187', '187',
template.render( template.render(
self.hass, '{{ distance("32.87336", "-117.22943") | round }}')) self.hass, '{{ distance("32.87336", "-117.22943") | round }}'))
def test_distance_function_with_2_coords(self): def test_distance_function_with_2_coords(self):
""".""" """Test distance function with 2 coords."""
self.assertEqual( self.assertEqual(
'187', '187',
template.render( template.render(
@ -337,7 +331,7 @@ class TestUtilTemplate(unittest.TestCase):
% (self.hass.config.latitude, self.hass.config.longitude))) % (self.hass.config.latitude, self.hass.config.longitude)))
def test_distance_function_with_1_state_1_coord(self): def test_distance_function_with_1_state_1_coord(self):
""".""" """Test distance function with 1 state 1 coord."""
self.hass.states.set('test.object_2', 'happy', { self.hass.states.set('test.object_2', 'happy', {
'latitude': self.hass.config.latitude, 'latitude': self.hass.config.latitude,
'longitude': self.hass.config.longitude, 'longitude': self.hass.config.longitude,
@ -358,7 +352,7 @@ class TestUtilTemplate(unittest.TestCase):
'| round }}')) '| round }}'))
def test_distance_function_return_None_if_invalid_state(self): def test_distance_function_return_None_if_invalid_state(self):
""".""" """Test distance function return None if invalid state."""
self.hass.states.set('test.object_2', 'happy', { self.hass.states.set('test.object_2', 'happy', {
'latitude': 10, 'latitude': 10,
}) })
@ -370,7 +364,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ distance(states.test.object_2) | round }}')) '{{ distance(states.test.object_2) | round }}'))
def test_distance_function_return_None_if_invalid_coord(self): def test_distance_function_return_None_if_invalid_coord(self):
""".""" """Test distance function return None if invalid coord."""
self.assertEqual( self.assertEqual(
'None', 'None',
template.render( template.render(
@ -395,7 +389,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ distance("123", states.test_object_2) }}')) '{{ distance("123", states.test_object_2) }}'))
def test_closest_function_home_vs_domain(self): def test_closest_function_home_vs_domain(self):
""".""" """Test closest function home vs domain."""
self.hass.states.set('test_domain.object', 'happy', { self.hass.states.set('test_domain.object', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -412,7 +406,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ closest(states.test_domain).entity_id }}')) '{{ closest(states.test_domain).entity_id }}'))
def test_closest_function_home_vs_all_states(self): def test_closest_function_home_vs_all_states(self):
""".""" """Test closest function home vs all states."""
self.hass.states.set('test_domain.object', 'happy', { self.hass.states.set('test_domain.object', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -429,7 +423,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ closest(states).entity_id }}')) '{{ closest(states).entity_id }}'))
def test_closest_function_home_vs_group_entity_id(self): def test_closest_function_home_vs_group_entity_id(self):
""".""" """Test closest function home vs group entity id."""
self.hass.states.set('test_domain.object', 'happy', { self.hass.states.set('test_domain.object', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -448,7 +442,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ closest("group.location_group").entity_id }}')) '{{ closest("group.location_group").entity_id }}'))
def test_closest_function_home_vs_group_state(self): def test_closest_function_home_vs_group_state(self):
""".""" """Test closest function home vs group state."""
self.hass.states.set('test_domain.object', 'happy', { self.hass.states.set('test_domain.object', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -468,7 +462,7 @@ class TestUtilTemplate(unittest.TestCase):
'{{ closest(states.group.location_group).entity_id }}')) '{{ closest(states.group.location_group).entity_id }}'))
def test_closest_function_to_coord(self): def test_closest_function_to_coord(self):
""".""" """Test closest function to coord."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -494,7 +488,7 @@ class TestUtilTemplate(unittest.TestCase):
) )
def test_closest_function_to_entity_id(self): def test_closest_function_to_entity_id(self):
""".""" """Test closest function to entity id."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -518,7 +512,7 @@ class TestUtilTemplate(unittest.TestCase):
) )
def test_closest_function_to_state(self): def test_closest_function_to_state(self):
""".""" """Test closest function to state."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -543,7 +537,7 @@ class TestUtilTemplate(unittest.TestCase):
) )
def test_closest_function_invalid_state(self): def test_closest_function_invalid_state(self):
""".""" """Test closest function invalid state."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -556,7 +550,7 @@ class TestUtilTemplate(unittest.TestCase):
self.hass, '{{ closest(%s, states) }}' % state)) self.hass, '{{ closest(%s, states) }}' % state))
def test_closest_function_state_with_invalid_location(self): def test_closest_function_state_with_invalid_location(self):
""".""" """Test closest function state with invalid location."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': 'invalid latitude', 'latitude': 'invalid latitude',
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -570,7 +564,7 @@ class TestUtilTemplate(unittest.TestCase):
'states) }}')) 'states) }}'))
def test_closest_function_invalid_coordinates(self): def test_closest_function_invalid_coordinates(self):
""".""" """Test closest function invalid coordinates."""
self.hass.states.set('test_domain.closest_home', 'happy', { self.hass.states.set('test_domain.closest_home', 'happy', {
'latitude': self.hass.config.latitude + 0.1, 'latitude': self.hass.config.latitude + 0.1,
'longitude': self.hass.config.longitude + 0.1, 'longitude': self.hass.config.longitude + 0.1,
@ -582,6 +576,16 @@ class TestUtilTemplate(unittest.TestCase):
'{{ closest("invalid", "coord", states) }}')) '{{ closest("invalid", "coord", states) }}'))
def test_closest_function_no_location_states(self): def test_closest_function_no_location_states(self):
""".""" """Test closest function without location states."""
self.assertEqual('None', self.assertEqual('None',
template.render(self.hass, '{{ closest(states) }}')) template.render(self.hass, '{{ closest(states) }}'))
def test_compiling_template(self):
"""Test compiling a template."""
self.hass.states.set('test_domain.hello', 'world')
compiled = template.compile_template(
self.hass, '{{ states.test_domain.hello.state }}')
with patch('homeassistant.helpers.template.compile_template',
side_effect=Exception('Should not be called')):
assert 'world' == template.render(self.hass, compiled)