Pre-compile templates (#3515)
* Pre-compile templates * Compile templates in numeric_state condition
This commit is contained in:
parent
b3d67a7ed9
commit
0c0feda834
15 changed files with 134 additions and 80 deletions
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
)])
|
)])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)])
|
)])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue