diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 608063b4708..840ebc12a13 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -12,7 +12,7 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID, CONF_BELOW, CONF_ABOVE) 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({ vol.Required(CONF_PLATFORM): 'numeric_state', @@ -31,6 +31,8 @@ def trigger(hass, config, action): below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) 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 def state_automation_listener(entity, from_s, to_s): diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 0891590a539..9d4ca08cb8e 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.const import ( 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 import homeassistant.helpers.config_validation as cv @@ -25,7 +25,8 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ def trigger(hass, config, action): """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 already_triggered = False diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index f56f9cb7a39..104e08bf290 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -46,6 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensor_class = config.get(CONF_SENSOR_CLASS) value_template = config.get(CONF_VALUE_TEMPLATE) + if value_template is not None: + value_template = template.compile_template(hass, value_template) + data = CommandSensorData(command) add_devices([CommandBinarySensor( diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index fd767bb1528..14fe43a7e7c 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -37,6 +37,11 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """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( hass, 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_PAYLOAD_ON), config.get(CONF_PAYLOAD_OFF), - config.get(CONF_VALUE_TEMPLATE) + value_template )]) diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 71666b91d06..527c7ae3599 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -45,6 +45,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensor_class = config.get(CONF_SENSOR_CLASS) 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.update() diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index e0b748bbbbe..207db511321 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -73,7 +73,7 @@ class BinarySensorTemplate(BinarySensorDevice): hass=hass) self._name = friendly_name self._sensor_class = sensor_class - self._template = value_template + self._template = template.compile_template(hass, value_template) self._state = None self.update() diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index 940f80a757b..dda4d60e342 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the template sensors.""" + """Setup the trend sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index 85a662065a6..41ace5096ed 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -38,18 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup a generic IP Camera.""" - add_devices([GenericCamera(config)]) + add_devices([GenericCamera(hass, config)]) # pylint: disable=too-many-instance-attributes class GenericCamera(Camera): """A generic implementation of an IP camera.""" - def __init__(self, device_info): + def __init__(self, hass, device_info): """Initialize a generic camera.""" super().__init__() + self.hass = hass 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] username = device_info.get(CONF_USERNAME) diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index 0a1da9d7a20..1b6a9270058 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -24,7 +24,7 @@ COVER_SCHEMA = vol.Schema({ vol.Optional(CONF_COMMAND_STATE): cv.string, vol.Optional(CONF_COMMAND_STOP, default='true'): 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({ @@ -38,6 +38,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): covers = [] 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( CommandCover( 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_STOP), device_config.get(CONF_COMMAND_STATE), - device_config.get(CONF_VALUE_TEMPLATE), + value_template, ) ) diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 3c1cf73eca0..aede4dd5e65 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -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_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): 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): """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( hass, 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_STOP), config.get(CONF_OPTIMISTIC), - config.get(CONF_VALUE_TEMPLATE) + value_template, )]) diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 743a1909ea5..92d1176202f 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -71,7 +71,7 @@ class SensorTemplate(Entity): hass=hass) self._name = friendly_name self._unit_of_measurement = unit_of_measurement - self._template = state_template + self._template = template.compile_template(hass, state_template) self._state = None self.update() diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 2b043c110a4..4a27d53f070 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -79,7 +79,7 @@ class SwitchTemplate(SwitchDevice): self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name - self._template = state_template + self._template = template.compile_template(hass, state_template) self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._state = False diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index f2c3585ec8b..2a26455ee76 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -16,7 +16,7 @@ from homeassistant.const import ( CONF_BELOW, CONF_ABOVE) from homeassistant.exceptions import TemplateError, HomeAssistantError 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 FROM_CONFIG_FORMAT = '{}_from_config' @@ -125,9 +125,18 @@ def numeric_state_from_config(config, config_validation=True): above = config.get(CONF_ABOVE) value_template = config.get(CONF_VALUE_TEMPLATE) + cache = {} + def if_numeric_state(hass, variables=None): """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) return if_numeric_state @@ -222,9 +231,16 @@ def template_from_config(config, config_validation=True): config = cv.TEMPLATE_CONDITION_SCHEMA(config) value_template = config.get(CONF_VALUE_TEMPLATE) + cache = {} + def template_if(hass, variables=None): """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 diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index e083534f828..92cd94ef4b0 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -18,6 +18,24 @@ _SENTINEL = object() 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, error_value=_SENTINEL): """Render template with value exposed. @@ -44,22 +62,11 @@ def render(hass, template, variables=None, **kwargs): if variables is not None: kwargs.update(variables) - location_methods = LocationMethods(hass) - utcnow = dt_util.utcnow() - try: - 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.as_local(utcnow), - 'states': AllStates(hass), - 'utcnow': utcnow, - 'as_timestamp': dt_util.as_timestamp, - 'relative_time': dt_util.get_age - }).render(kwargs).strip() + if not isinstance(template, jinja2.Template): + template = compile_template(hass, template) + + return template.render(kwargs).strip() except jinja2.TemplateError as err: raise TemplateError(err) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 64bac46264d..3c035bea3e5 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -33,14 +33,14 @@ class TestUtilTemplate(unittest.TestCase): self.hass.stop() def test_referring_states_by_entity_id(self): - """.""" + """Test referring states by entity id.""" self.hass.states.set('test.object', 'happy') self.assertEqual( 'happy', template.render(self.hass, '{{ states.test.object.state }}')) def test_iterating_all_states(self): - """.""" + """Test iterating all states.""" self.hass.states.set('test.object', 'happy') self.hass.states.set('sensor.temperature', 10) @@ -51,7 +51,7 @@ class TestUtilTemplate(unittest.TestCase): '{% for state in states %}{{ state.state }}{% endfor %}')) def test_iterating_domain_states(self): - """.""" + """Test iterating domain states.""" self.hass.states.set('test.object', 'happy') self.hass.states.set('sensor.back_door', 'open') self.hass.states.set('sensor.temperature', 10) @@ -65,7 +65,7 @@ class TestUtilTemplate(unittest.TestCase): """)) def test_float(self): - """.""" + """Test float.""" self.hass.states.set('sensor.temperature', '12') self.assertEqual( @@ -81,7 +81,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ float(states.sensor.temperature.state) > 11 }}')) def test_rounding_value(self): - """.""" + """Test rounding value.""" self.hass.states.set('sensor.temperature', 12.78) self.assertEqual( @@ -98,7 +98,7 @@ class TestUtilTemplate(unittest.TestCase): )) def test_rounding_value_get_original_value_on_error(self): - """.""" + """Test rounding value get original value on error.""" self.assertEqual( 'None', template.render( @@ -114,7 +114,7 @@ class TestUtilTemplate(unittest.TestCase): )) def test_multiply(self): - """.""" + """Test multiply.""" tests = { None: 'None', 10: '100', @@ -180,50 +180,50 @@ class TestUtilTemplate(unittest.TestCase): '{{ %s | timestamp_utc }}' % inp)) def test_passing_vars_as_keywords(self): - """.""" + """Test passing variables as keywords.""" self.assertEqual( '127', template.render(self.hass, '{{ hello }}', hello=127)) def test_passing_vars_as_vars(self): - """.""" + """Test passing variables as variables.""" self.assertEqual( '127', template.render(self.hass, '{{ hello }}', {'hello': 127})) def test_render_with_possible_json_value_with_valid_json(self): - """.""" + """Render with possible JSON value with valid JSON.""" self.assertEqual( 'world', template.render_with_possible_json_value( self.hass, '{{ value_json.hello }}', '{"hello": "world"}')) def test_render_with_possible_json_value_with_invalid_json(self): - """.""" + """Render with possible JSON value with invalid JSON.""" self.assertEqual( '', template.render_with_possible_json_value( self.hass, '{{ value_json }}', '{ I AM NOT JSON }')) def test_render_with_possible_json_value_with_template_error(self): - """.""" + """Render with possible JSON value with template error.""" self.assertEqual( 'hello', template.render_with_possible_json_value( self.hass, '{{ value_json', 'hello')) def test_render_with_possible_json_value_with_template_error_value(self): - """.""" + """Render with possible JSON value with template error value.""" self.assertEqual( '-', template.render_with_possible_json_value( self.hass, '{{ value_json', 'hello', '-')) def test_raise_exception_on_error(self): - """.""" + """Test raising an exception on error.""" with self.assertRaises(TemplateError): template.render(self.hass, '{{ invalid_syntax') def test_if_state_exists(self): - """.""" + """Test if state exists works.""" self.hass.states.set('test.object', 'available') self.assertEqual( 'exists', @@ -234,7 +234,7 @@ class TestUtilTemplate(unittest.TestCase): """)) def test_is_state(self): - """.""" + """Test is_state method.""" self.hass.states.set('test.object', 'available') self.assertEqual( 'yes', @@ -245,7 +245,7 @@ class TestUtilTemplate(unittest.TestCase): """)) def test_is_state_attr(self): - """.""" + """Test is_state_attr method.""" self.hass.states.set('test.object', 'available', {'mode': 'on'}) self.assertEqual( 'yes', @@ -256,7 +256,7 @@ class TestUtilTemplate(unittest.TestCase): """)) def test_states_function(self): - """.""" + """Test using states as a function.""" self.hass.states.set('test.object', 'available') self.assertEqual( 'available', @@ -265,32 +265,26 @@ class TestUtilTemplate(unittest.TestCase): 'unknown', 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.' 'is_safe_callable', return_value=True) def test_now(self, mock_is_safe, mock_utcnow): - """.""" + """Test now method.""" self.assertEqual( - dt_util.utcnow().isoformat(), - template.render(self.hass, '{{ now.isoformat() }}')) + dt_util.now().isoformat(), + template.render(self.hass, '{{ now().isoformat() }}')) @patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow()) @patch('homeassistant.helpers.template.TemplateEnvironment.' 'is_safe_callable', return_value=True) def test_utcnow(self, mock_is_safe, mock_utcnow): - """.""" + """Test utcnow method.""" self.assertEqual( dt_util.utcnow().isoformat(), - template.render(self.hass, '{{ utcnow.isoformat() }}')) - - def test_utcnow_is_exactly_now(self): - """.""" - self.assertEqual( - 'True', - template.render(self.hass, '{{ utcnow == now }}')) + template.render(self.hass, '{{ utcnow().isoformat() }}')) def test_distance_function_with_1_state(self): - """.""" + """Test distance function with 1 state.""" self.hass.states.set('test.object', 'happy', { 'latitude': 32.87336, 'longitude': -117.22943, @@ -302,7 +296,7 @@ class TestUtilTemplate(unittest.TestCase): self.hass, '{{ distance(states.test.object) | round }}')) def test_distance_function_with_2_states(self): - """.""" + """Test distance function with 2 states.""" self.hass.states.set('test.object', 'happy', { 'latitude': 32.87336, 'longitude': -117.22943, @@ -321,14 +315,14 @@ class TestUtilTemplate(unittest.TestCase): '| round }}')) def test_distance_function_with_1_coord(self): - """.""" + """Test distance function with 1 coord.""" self.assertEqual( '187', template.render( self.hass, '{{ distance("32.87336", "-117.22943") | round }}')) def test_distance_function_with_2_coords(self): - """.""" + """Test distance function with 2 coords.""" self.assertEqual( '187', template.render( @@ -337,7 +331,7 @@ class TestUtilTemplate(unittest.TestCase): % (self.hass.config.latitude, self.hass.config.longitude))) 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', { 'latitude': self.hass.config.latitude, 'longitude': self.hass.config.longitude, @@ -358,7 +352,7 @@ class TestUtilTemplate(unittest.TestCase): '| round }}')) 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', { 'latitude': 10, }) @@ -370,7 +364,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ distance(states.test.object_2) | round }}')) def test_distance_function_return_None_if_invalid_coord(self): - """.""" + """Test distance function return None if invalid coord.""" self.assertEqual( 'None', template.render( @@ -395,7 +389,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ distance("123", states.test_object_2) }}')) def test_closest_function_home_vs_domain(self): - """.""" + """Test closest function home vs domain.""" self.hass.states.set('test_domain.object', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -412,7 +406,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ closest(states.test_domain).entity_id }}')) def test_closest_function_home_vs_all_states(self): - """.""" + """Test closest function home vs all states.""" self.hass.states.set('test_domain.object', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -429,7 +423,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ closest(states).entity_id }}')) 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', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -448,7 +442,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ closest("group.location_group").entity_id }}')) def test_closest_function_home_vs_group_state(self): - """.""" + """Test closest function home vs group state.""" self.hass.states.set('test_domain.object', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -468,7 +462,7 @@ class TestUtilTemplate(unittest.TestCase): '{{ closest(states.group.location_group).entity_id }}')) def test_closest_function_to_coord(self): - """.""" + """Test closest function to coord.""" self.hass.states.set('test_domain.closest_home', 'happy', { 'latitude': self.hass.config.latitude + 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): - """.""" + """Test closest function to entity id.""" self.hass.states.set('test_domain.closest_home', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -518,7 +512,7 @@ class TestUtilTemplate(unittest.TestCase): ) def test_closest_function_to_state(self): - """.""" + """Test closest function to state.""" self.hass.states.set('test_domain.closest_home', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -543,7 +537,7 @@ class TestUtilTemplate(unittest.TestCase): ) def test_closest_function_invalid_state(self): - """.""" + """Test closest function invalid state.""" self.hass.states.set('test_domain.closest_home', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -556,7 +550,7 @@ class TestUtilTemplate(unittest.TestCase): self.hass, '{{ closest(%s, states) }}' % state)) 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', { 'latitude': 'invalid latitude', 'longitude': self.hass.config.longitude + 0.1, @@ -570,7 +564,7 @@ class TestUtilTemplate(unittest.TestCase): 'states) }}')) def test_closest_function_invalid_coordinates(self): - """.""" + """Test closest function invalid coordinates.""" self.hass.states.set('test_domain.closest_home', 'happy', { 'latitude': self.hass.config.latitude + 0.1, 'longitude': self.hass.config.longitude + 0.1, @@ -582,6 +576,16 @@ class TestUtilTemplate(unittest.TestCase): '{{ closest("invalid", "coord", states) }}')) def test_closest_function_no_location_states(self): - """.""" + """Test closest function without location states.""" self.assertEqual('None', 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)