Optimize template 2 ()

* Enforce compiling templates

* Refactor templates

* Add template validator to Logbook service

* Some more fixes

* Lint

* Allow easy skipping of rfxtrx tests

* Fix template bug in AND & OR conditions

* add entities extractor

Conflicts:
	tests/helpers/test_template.py

* fix unittest

* Convert template to be async

* Fix Farcy

* Lint fix

* Limit template updates to related entities

* Make template automation async
pull/3561/head
Paulus Schoutsen 2016-09-27 21:29:55 -07:00 committed by GitHub
parent 6694b0470e
commit 00e298206e
52 changed files with 841 additions and 562 deletions

View File

@ -4,11 +4,14 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
import copy
import enum
import logging
import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
@ -20,10 +23,49 @@ CONF_CARD = 'card'
CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_TYPE = 'type'
CONF_TITLE = 'title'
CONF_CONTENT = 'content'
CONF_TEXT = 'text'
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_CARD): {
vol.Required(CONF_TYPE): cv.enum(CardType),
vol.Required(CONF_TITLE): cv.template,
vol.Required(CONF_CONTENT): cv.template,
},
vol.Optional(CONF_SPEECH): {
vol.Required(CONF_TYPE): cv.enum(SpeechType),
vol.Required(CONF_TEXT): cv.template,
}
}
}
}
})
def setup(hass, config):
"""Activate Alexa component."""
hass.wsgi.register_view(AlexaView(hass,
@ -42,6 +84,9 @@ class AlexaView(HomeAssistantView):
"""Initialize Alexa view."""
super().__init__(hass)
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
@ -101,29 +146,15 @@ class AlexaView(HomeAssistantView):
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text'])
response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
if card is not None:
response.add_card(CardType[card['type']], card['title'],
card['content'])
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
card[CONF_CONTENT])
return self.json(response)
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
class AlexaResponse(object):
"""Help generating the response for Alexa."""
@ -153,8 +184,8 @@ class AlexaResponse(object):
self.card = card
return
card["title"] = self._render(title),
card["content"] = self._render(content)
card["title"] = title.render(self.variables)
card["content"] = content.render(self.variables)
self.card = card
def add_speech(self, speech_type, text):
@ -163,9 +194,12 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.render(self.variables)
self.speech = {
'type': speech_type.value,
key: self._render(text)
key: text
}
def add_reprompt(self, speech_type, text):
@ -176,7 +210,7 @@ class AlexaResponse(object):
self.reprompt = {
'type': speech_type.value,
key: self._render(text)
key: text.render(self.variables)
}
def as_dict(self):
@ -201,7 +235,3 @@ class AlexaResponse(object):
'sessionAttributes': self.session_attributes,
'response': response,
}
def _render(self, template_string):
"""Render a response, adding data from intent if available."""
return template.render(self.hass, template_string, self.variables)

View File

@ -378,8 +378,8 @@ class APITemplateView(HomeAssistantView):
def post(self, request):
"""Render a template."""
try:
return template.render(self.hass, request.json['template'],
request.json.get('variables'))
tpl = template.Template(request.json['template'], self.hass)
return tpl.render(request.json.get('variables'))
except TemplateError as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)

View File

@ -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, template
from homeassistant.helpers import condition, config_validation as cv
TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'numeric_state',
@ -32,7 +32,7 @@ def trigger(hass, config, action):
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)
value_template.hass = hass
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):

View File

@ -4,13 +4,13 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
from homeassistant.helpers import condition, template
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
@ -25,21 +25,22 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
def trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = template.compile_template(
hass, config.get(CONF_VALUE_TEMPLATE))
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
# Local variable to keep track of if the action has already been triggered
already_triggered = False
@asyncio.coroutine
def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
template_result = condition.template(hass, value_template)
template_result = condition.async_template(hass, value_template)
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
action({
hass.async_add_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
@ -50,4 +51,5 @@ def trigger(hass, config, action):
elif not template_result:
already_triggered = False
return track_state_change(hass, MATCH_ALL, state_changed_listener)
return track_state_change(hass, value_template.extract_entities(),
state_changed_listener)

View File

@ -15,7 +15,6 @@ from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -45,10 +44,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload_on = config.get(CONF_PAYLOAD_ON)
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)
value_template.hass = hass
data = CommandSensorData(command)
add_devices([CommandBinarySensor(
@ -94,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
value = self.data.value
if self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, False)
value = self._value_template.render_with_possible_json_value(
value, False)
if value == self._payload_on:
self._state = True
elif value == self._payload_off:

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -38,10 +37,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
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)
value_template.hass = hass
add_devices([MqttBinarySensor(
hass,
config.get(CONF_NAME),
@ -73,8 +70,8 @@ class MqttBinarySensor(BinarySensorDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._payload_on:
self._state = True
self.update_ha_state()

View File

@ -14,7 +14,6 @@ from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -44,10 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
verify_ssl = config.get(CONF_VERIFY_SSL)
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)
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
@ -91,8 +88,8 @@ class RestBinarySensor(BinarySensorDevice):
return False
if self._value_template is not None:
response = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
response = self._value_template.render_with_possible_json_value(
self.rest.data, False)
try:
return bool(int(response))

View File

@ -12,10 +12,9 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, MATCH_ALL, CONF_VALUE_TEMPLATE,
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
@ -25,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
})
@ -40,10 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID]
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config.get(CONF_SENSOR_CLASS)
if value_template is not None:
value_template.hass = hass
sensors.append(
BinarySensorTemplate(
hass,
@ -73,7 +76,7 @@ class BinarySensorTemplate(BinarySensorDevice):
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._template = template.compile_template(hass, value_template)
self._template = value_template
self._state = None
self.update()
@ -107,8 +110,7 @@ class BinarySensorTemplate(BinarySensorDevice):
def update(self):
"""Get the latest data and update the state."""
try:
self._state = template.render(
self.hass, self._template).lower() == 'true'
self._state = self._template.render().lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):

View File

@ -2,7 +2,7 @@
A sensor that monitors trands in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/
https://home-assistant.io/components/sensor.trend/
"""
import logging
import voluptuous as vol
@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SensorTrend(BinarySensorDevice):
"""Representation of a Template Sensor."""
"""Representation of a trend Sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, device_id, friendly_name,
@ -90,14 +90,14 @@ class SensorTrend(BinarySensorDevice):
self.update()
def template_sensor_state_listener(entity, old_state, new_state):
def trend_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.from_state = old_state
self.to_state = new_state
self.update_ha_state(True)
track_state_change(hass, target_entity,
template_sensor_state_listener)
trend_sensor_state_listener)
@property
def name(self):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
DEFAULT_NAME = 'Generic Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STILL_IMAGE_URL): vol.Any(cv.url, cv.template),
vol.Required(CONF_STILL_IMAGE_URL): cv.template,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
@ -50,8 +50,8 @@ class GenericCamera(Camera):
super().__init__()
self.hass = hass
self._name = device_info.get(CONF_NAME)
self._still_image_url = template.compile_template(
hass, device_info[CONF_STILL_IMAGE_URL])
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME)
@ -71,7 +71,7 @@ class GenericCamera(Camera):
def camera_image(self):
"""Return a still image response from the camera."""
try:
url = template.render(self.hass, self._still_image_url)
url = self._still_image_url.render()
except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err)

View File

@ -14,7 +14,6 @@ from homeassistant.const import (
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
@ -39,9 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
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)
value_template.hass = hass
covers.append(
CommandCover(
@ -141,8 +138,8 @@ class CommandCover(CoverDevice):
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
payload = self._value_template.render_with_possible_json_value(
payload)
self._state = int(payload)
def open_cover(self, **kwargs):

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
STATE_CLOSED)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -49,10 +48,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
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)
value_template.hass = hass
add_devices([MqttCover(
hass,
config.get(CONF_NAME),
@ -96,8 +93,8 @@ class MqttCover(CoverDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._state_open:
self._state = False
_LOGGER.warning("state=%s", int(self._state))

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/fan.mqtt/
"""
import logging
from functools import partial
import voluptuous as vol
@ -16,7 +15,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
SPEED_HIGH, FanEntity,
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
@ -139,9 +137,12 @@ class MqttFan(FanEntity):
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED)
templates = {key: ((lambda value: value) if tpl is None else
partial(render_with_possible_json_value, hass, tpl))
for key, tpl in templates.items()}
for key, tpl in list(templates.items()):
if tpl is None:
templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
def state_received(topic, payload, qos):
"""A new MQTT message has been received."""

View File

@ -16,7 +16,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
@ -45,6 +44,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Garage Door."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices_callback([MqttGarageDoor(
hass,
config[CONF_NAME],
@ -57,7 +59,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
config[CONF_SERVICE_OPEN],
config[CONF_SERVICE_CLOSE],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))])
value_template)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
@ -84,8 +86,8 @@ class MqttGarageDoor(GarageDoorDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._state_open:
self._state = True
self.update_ha_state()

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mqtt/
"""
import logging
from functools import partial
import voluptuous as vol
@ -19,7 +18,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
_LOGGER = logging.getLogger(__name__)
@ -117,9 +115,12 @@ class MqttLight(Light):
topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and
SUPPORT_BRIGHTNESS)
templates = {key: ((lambda value: value) if tpl is None else
partial(render_with_possible_json_value, hass, tpl))
for key, tpl in templates.items()}
for key, tpl in list(templates.items()):
if tpl is None:
templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
def state_received(topic, payload, qos):
"""A new MQTT message has been received."""

View File

@ -13,7 +13,6 @@ from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
from homeassistant.helpers import template
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv
@ -42,6 +41,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT lock."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttLock(
hass,
config.get(CONF_NAME),
@ -52,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_LOCK),
config.get(CONF_PAYLOAD_UNLOCK),
config.get(CONF_OPTIMISTIC),
config.get(CONF_VALUE_TEMPLATE)
value_template,
)])
@ -77,8 +79,8 @@ class MqttLock(LockDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._payload_lock:
self._state = True
self.update_ha_state()

View File

@ -20,7 +20,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
STATE_NOT_HOME, STATE_OFF, STATE_ON,
ATTR_HIDDEN)
from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN
from homeassistant.helpers import template
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'frontend']
@ -51,7 +50,7 @@ ATTR_ENTITY_ID = 'entity_id'
LOG_MESSAGE_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_MESSAGE): cv.string,
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_DOMAIN): cv.slug,
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
})
@ -80,7 +79,8 @@ def setup(hass, config):
domain = service.data.get(ATTR_DOMAIN)
entity_id = service.data.get(ATTR_ENTITY_ID)
message = template.render(hass, message)
message.hass = hass
message = message.render()
log_entry(hass, name, message, domain, entity_id)
hass.wsgi.register_view(LogbookView(hass, config))

View File

@ -264,8 +264,8 @@ def setup(hass, config):
qos = call.data[ATTR_QOS]
retain = call.data[ATTR_RETAIN]
try:
payload = (payload if payload_template is None else
template.render(hass, payload_template)) or ''
if payload_template is not None:
payload = template.Template(payload_template, hass).render()
except template.jinja2.TemplateError as exc:
_LOGGER.error(
"Unable to publish to '%s': rendering payload template of "

View File

@ -12,7 +12,7 @@ import voluptuous as vol
import homeassistant.bootstrap as bootstrap
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_per_platform, template
from homeassistant.helpers import config_per_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.util import slugify
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = vol.Schema({
NOTIFY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.string,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_TARGET): cv.string,
vol.Optional(ATTR_DATA): dict,
})
@ -96,14 +96,16 @@ def setup(hass, config):
title = call.data.get(ATTR_TITLE)
if title:
kwargs[ATTR_TITLE] = template.render(hass, title)
title.hass = hass
kwargs[ATTR_TITLE] = title.render()
if targets.get(call.service) is not None:
kwargs[ATTR_TARGET] = targets[call.service]
else:
kwargs[ATTR_TARGET] = call.data.get(ATTR_TARGET)
kwargs[ATTR_MESSAGE] = template.render(hass, message)
message.hass = hass
kwargs[ATTR_MESSAGE] = message.render()
kwargs[ATTR_DATA] = call.data.get(ATTR_DATA)
notify_service.send_message(**kwargs)

View File

@ -10,7 +10,7 @@ import logging
import voluptuous as vol
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import slugify
from homeassistant.config import load_yaml_config_file
@ -63,16 +63,20 @@ def setup(hass, config):
attr = {}
if title is not None:
try:
title = template.render(hass, title)
title.hass = hass
title = title.render()
except TemplateError as ex:
_LOGGER.error('Error rendering title %s: %s', title, ex)
title = title.template
attr[ATTR_TITLE] = title
try:
message = template.render(hass, message)
message.hass = hass
message = message.render()
except TemplateError as ex:
_LOGGER.error('Error rendering message %s: %s', message, ex)
message = message.template
hass.states.set(entity_id, message, attr)

View File

@ -9,7 +9,7 @@ import subprocess
from homeassistant.components.rollershutter import RollershutterDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template
from homeassistant.helpers.template import Template
_LOGGER = logging.getLogger(__name__)
@ -20,6 +20,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
devices = []
for dev_name, properties in rollershutters.items():
value_template = properties.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = Template(value_template, hass)
devices.append(
CommandRollershutter(
hass,
@ -28,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
properties.get('downcmd', 'true'),
properties.get('stopcmd', 'true'),
properties.get('statecmd', False),
properties.get(CONF_VALUE_TEMPLATE, '{{ value }}')))
value_template))
add_devices_callback(devices)
@ -103,8 +108,8 @@ class CommandRollershutter(RollershutterDevice):
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
payload = self._value_template.render_with_possible_json_value(
payload)
self._state = int(payload)
def move_up(self, **kwargs):

View File

@ -13,7 +13,6 @@ from homeassistant.components.rollershutter import RollershutterDevice
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -39,6 +38,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Rollershutter."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices_callback([MqttRollershutter(
hass,
config[CONF_NAME],
@ -48,7 +50,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
config[CONF_PAYLOAD_UP],
config[CONF_PAYLOAD_DOWN],
config[CONF_PAYLOAD_STOP],
config.get(CONF_VALUE_TEMPLATE)
value_template,
)])
@ -76,8 +78,8 @@ class MqttRollershutter(RollershutterDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload.isnumeric() and 0 <= int(payload) <= 100:
self._state = int(payload)
self.update_ha_state()

View File

@ -52,9 +52,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if value_template is None:
return lambda value: value
value_template = template.Template(value_template, hass)
def _render(value):
try:
return template.render(hass, value_template, {'value': value})
return value_template.render({'value': value})
except TemplateError:
_LOGGER.exception('Error parsing value')
return value

View File

@ -14,7 +14,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
@ -39,7 +38,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
command = config.get(CONF_COMMAND)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(command)
add_devices([CommandSensor(hass, data, name, unit, value_template)])
@ -80,8 +80,8 @@ class CommandSensor(Entity):
value = self.data.value
if self._value_template is not None:
self._state = template.render_with_possible_json_value(
self._hass, self._value_template, value, 'N/A')
self._state = self._value_template.render_with_possible_json_value(
value, 'N/A')
else:
self._state = value

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
from homeassistant.util import Throttle
REQUIREMENTS = ['dweepy==0.2.0']
@ -45,16 +44,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device = config.get(CONF_DEVICE)
value_template = config.get(CONF_VALUE_TEMPLATE)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template.hass = hass
try:
content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content'])
except dweepy.DweepyError:
_LOGGER.error("Device/thing '%s' could not be found", device)
return False
if template.render_with_possible_json_value(hass,
value_template,
content) is '':
if value_template.render_with_possible_json_value(content) == '':
_LOGGER.error("'%s' was not found", value_template)
return False
@ -94,8 +91,8 @@ class DweetSensor(Entity):
return STATE_UNKNOWN
else:
values = json.dumps(self.dweet.data[0]['content'])
value = template.render_with_possible_json_value(
self.hass, self._value_template, values)
value = self._value_template.render_with_possible_json_value(
values)
return value
def update(self):

View File

@ -70,6 +70,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_names = config.get(CONF_SENSOR_NAMES)
interval = config.get(CONF_SCAN_INTERVAL)
if value_template is not None:
value_template.hass = hass
data = EmonCmsData(hass, url, apikey, interval)
data.update()
@ -123,9 +126,8 @@ class EmonCmsSensor(Entity):
self._elem = elem
if self._value_template is not None:
self._state = template.render_with_possible_json_value(
self._hass, self._value_template, elem["value"],
STATE_UNKNOWN)
self._state = self._value_template.render_with_possible_json_value(
elem["value"], STATE_UNKNOWN)
else:
self._state = round(float(elem["value"]), DECIMALS)
@ -177,9 +179,8 @@ class EmonCmsSensor(Entity):
self._elem = elem
if self._value_template is not None:
self._state = template.render_with_possible_json_value(
self._hass, self._value_template, elem["value"],
STATE_UNKNOWN)
self._state = self._value_template.render_with_possible_json_value(
elem["value"], STATE_UNKNOWN)
else:
self._state = round(float(elem["value"]), DECIMALS)

View File

@ -12,16 +12,14 @@ from collections import deque
from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD)
CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_VALUE_TEMPLATE)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
import voluptuous as vol
_LOGGER = logging.getLogger(__name__)
CONF_SERVER = "server"
CONF_SENDERS = "senders"
CONF_VALUE_TEMPLATE = "value_template"
ATTR_FROM = "from"
ATTR_BODY = "body"
@ -48,12 +46,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_SERVER),
config.get(CONF_PORT))
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
sensor = EmailContentSensor(
hass,
reader,
config.get(CONF_NAME, None) or config.get(CONF_USERNAME),
config.get(CONF_SENDERS),
config.get(CONF_VALUE_TEMPLATE))
value_template)
if sensor.connected:
add_devices([sensor])
@ -172,7 +173,7 @@ class EmailContentSensor(Entity):
ATTR_DATE: email_message['Date'],
ATTR_BODY: EmailContentSensor.get_msg_text(email_message)
}
return template.render(self.hass, self._value_template, variables)
return self._value_template.render(variables)
def sender_allowed(self, email_message):
"""Check if the sender is in the allowed senders list."""

View File

@ -11,7 +11,6 @@ import voluptuous as vol
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
from homeassistant.helpers import template
from homeassistant.helpers.entity import Entity
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv
@ -30,13 +29,16 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup MQTT Sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttSensor(
hass,
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_QOS),
config.get(CONF_UNIT_OF_MEASUREMENT),
config.get(CONF_VALUE_TEMPLATE),
value_template,
)])
@ -57,8 +59,8 @@ class MqttSensor(Entity):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
self._state = payload
self.update_ha_state()

View File

@ -14,7 +14,6 @@ from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, CONF_VERIFY_SSL)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -44,7 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
verify_ssl = config.get(CONF_VERIFY_SSL)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
@ -92,8 +92,8 @@ class RestSensor(Entity):
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, STATE_UNKNOWN)
value = self._value_template.render_with_possible_json_value(
value, STATE_UNKNOWN)
self._state = value

View File

@ -9,9 +9,9 @@ import socket
import select
from homeassistant.const import CONF_NAME, CONF_HOST
from homeassistant.helpers import template
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
CONF_PORT = "port"
CONF_TIMEOUT = "timeout"
@ -41,6 +41,11 @@ class Sensor(Entity):
def __init__(self, hass, config):
"""Set all the config values if they exist and get initial state."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template = Template(value_template, hass)
self._hass = hass
self._config = {
CONF_NAME: config.get(CONF_NAME),
@ -49,7 +54,7 @@ class Sensor(Entity):
CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
CONF_PAYLOAD: config[CONF_PAYLOAD],
CONF_UNIT: config.get(CONF_UNIT),
CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE),
CONF_VALUE_TEMPLATE: value_template,
CONF_VALUE_ON: config.get(CONF_VALUE_ON),
CONF_BUFFER_SIZE: config.get(
CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE),
@ -122,9 +127,7 @@ class Sensor(Entity):
if self._config[CONF_VALUE_TEMPLATE] is not None:
try:
self._state = template.render(
self._hass,
self._config[CONF_VALUE_TEMPLATE],
self._state = self._config[CONF_VALUE_TEMPLATE].render(
value=value)
return
except TemplateError as err:

View File

@ -11,9 +11,8 @@ import voluptuous as vol
from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
ATTR_ENTITY_ID, MATCH_ALL, CONF_SENSORS)
ATTR_ENTITY_ID, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
@ -24,7 +23,7 @@ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -39,10 +38,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for device, device_config in config[CONF_SENSORS].items():
state_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID]
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
state_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
state_template.hass = hass
sensors.append(
SensorTemplate(
hass,
@ -71,7 +73,7 @@ class SensorTemplate(Entity):
hass=hass)
self._name = friendly_name
self._unit_of_measurement = unit_of_measurement
self._template = template.compile_template(hass, state_template)
self._template = state_template
self._state = None
self.update()
@ -105,7 +107,7 @@ class SensorTemplate(Entity):
def update(self):
"""Get the latest data and update the states."""
try:
self._state = template.render(self.hass, self._template)
self._state = self._template.render()
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):

View File

@ -29,12 +29,41 @@ def setup(hass, config):
"""Setup the shell_command component."""
conf = config.get(DOMAIN, {})
cache = {}
def service_handler(call):
"""Execute a shell command service."""
cmd = conf[call.service]
cmd, shell = _parse_command(hass, cmd, call.data)
if cmd is None:
return
if cmd in cache:
prog, args, args_compiled = cache[cmd]
elif ' ' not in cmd:
prog = cmd
args = None
args_compiled = None
cache[cmd] = prog, args, args_compiled
else:
prog, args = cmd.split(' ', 1)
args_compiled = template.Template(args, hass)
cache[cmd] = prog, args, args_compiled
if args_compiled:
try:
rendered_args = args_compiled.render(call.data)
except TemplateError as ex:
_LOGGER.exception('Error rendering command template: %s', ex)
return
else:
rendered_args = None
if rendered_args == args:
# no template used. default behavior
shell = True
else:
# template used. Break into list and use shell=False for security
cmd = [prog] + shlex.split(rendered_args)
shell = False
try:
subprocess.call(cmd, shell=shell,
stdout=subprocess.DEVNULL,
@ -45,23 +74,3 @@ def setup(hass, config):
for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler)
return True
def _parse_command(hass, cmd, variables):
"""Parse command and fill in any template arguments if necessary."""
cmds = cmd.split()
prog = cmds[0]
args = ' '.join(cmds[1:])
try:
rendered_args = template.render(hass, args, variables=variables)
except TemplateError as ex:
_LOGGER.exception('Error rendering command template: %s', ex)
return None, None
if rendered_args == args:
# no template used. default behavior
shell = True
else:
# template used. Must break into list and use shell=False for security
cmd = [prog] + shlex.split(rendered_args)
shell = False
return cmd, shell

View File

@ -13,7 +13,6 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF,
CONF_COMMAND_ON, CONF_COMMAND_STATE)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -38,6 +37,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
switches = []
for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
switches.append(
CommandSwitch(
hass,
@ -45,7 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device_config.get(CONF_COMMAND_ON),
device_config.get(CONF_COMMAND_OFF),
device_config.get(CONF_COMMAND_STATE),
device_config.get(CONF_VALUE_TEMPLATE)
value_template,
)
)
@ -135,8 +139,8 @@ class CommandSwitch(SwitchDevice):
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
payload = self._value_template.render_with_possible_json_value(
payload)
self._state = (payload.lower() == "true")
def turn_on(self, **kwargs):

View File

@ -14,7 +14,6 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON)
from homeassistant.helpers import template
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv
@ -38,6 +37,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT switch."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttSwitch(
hass,
config.get(CONF_NAME),
@ -48,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_OPTIMISTIC),
config.get(CONF_VALUE_TEMPLATE)
value_template,
)])
@ -73,8 +75,8 @@ class MqttSwitch(SwitchDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._payload_on:
self._state = True
self.update_ha_state()

View File

@ -12,9 +12,8 @@ from homeassistant.components.switch import (
ENTITY_ID_FORMAT, SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
ATTR_ENTITY_ID, MATCH_ALL, CONF_SWITCHES)
ATTR_ENTITY_ID, CONF_SWITCHES)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.script import Script
@ -31,7 +30,7 @@ SWITCH_SCHEMA = vol.Schema({
vol.Required(ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -49,7 +48,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
state_template = device_config[CONF_VALUE_TEMPLATE]
on_action = device_config[ON_ACTION]
off_action = device_config[OFF_ACTION]
entity_ids = device_config[ATTR_ENTITY_ID]
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
state_template.extract_entities())
switches.append(
SwitchTemplate(
@ -79,7 +79,8 @@ class SwitchTemplate(SwitchDevice):
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self._name = friendly_name
self._template = template.compile_template(hass, state_template)
self._template = state_template
state_template.hass = hass
self._on_script = Script(hass, on_action)
self._off_script = Script(hass, off_action)
self._state = False
@ -123,7 +124,7 @@ class SwitchTemplate(SwitchDevice):
def update(self):
"""Update the state from the template."""
try:
state = template.render(self.hass, self._template).lower()
state = self._template.render().lower()
if state in _VALID_STATES:
self._state = state in ('true', STATE_ON)

View File

@ -16,8 +16,8 @@ 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, compile_template
import homeassistant.util.dt as dt_util
from homeassistant.util.async import run_callback_threadsafe
FROM_CONFIG_FORMAT = '{}_from_config'
@ -41,7 +41,7 @@ def and_from_config(config: ConfigType, config_validation: bool=True):
"""Create multi condition matcher using 'AND'."""
if config_validation:
config = cv.AND_CONDITION_SCHEMA(config)
checks = [from_config(entry) for entry in config['conditions']]
checks = [from_config(entry, False) for entry in config['conditions']]
def if_and_condition(hass: HomeAssistant,
variables=None) -> bool:
@ -63,7 +63,7 @@ def or_from_config(config: ConfigType, config_validation: bool=True):
"""Create multi condition matcher using 'OR'."""
if config_validation:
config = cv.OR_CONDITION_SCHEMA(config)
checks = [from_config(entry) for entry in config['conditions']]
checks = [from_config(entry, False) for entry in config['conditions']]
def if_or_condition(hass: HomeAssistant,
variables=None) -> bool:
@ -96,7 +96,7 @@ def numeric_state(hass: HomeAssistant, entity, below=None, above=None,
variables = dict(variables or {})
variables['state'] = entity
try:
value = render(hass, value_template, variables)
value = value_template.render(variables)
except TemplateError as ex:
_LOGGER.error("Template error: %s", ex)
return False
@ -125,18 +125,12 @@ 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."""
if value_template is None:
tmpl = None
elif hass in cache:
tmpl = cache[hass]
else:
cache[hass] = tmpl = compile_template(hass, value_template)
if value_template is not None:
value_template.hass = hass
return numeric_state(hass, entity_id, below, above, tmpl,
return numeric_state(hass, entity_id, below, above, value_template,
variables)
return if_numeric_state
@ -215,9 +209,16 @@ def sun_from_config(config, config_validation=True):
def template(hass, value_template, variables=None):
"""Test if template condition matches."""
return run_callback_threadsafe(
hass.loop, async_template, hass, value_template, variables,
).result()
def async_template(hass, value_template, variables=None):
"""Test if template condition matches."""
try:
value = render(hass, value_template, variables)
value = value_template.async_render(variables)
except TemplateError as ex:
_LOGGER.error('Error duriong template condition: %s', ex)
return False
@ -231,16 +232,11 @@ 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."""
if hass in cache:
tmpl = cache[hass]
else:
cache[hass] = tmpl = compile_template(hass, value_template)
value_template.hass = hass
return template(hass, tmpl, variables)
return template(hass, value_template, variables)
return template_if

View File

@ -6,7 +6,6 @@ from urllib.parse import urlparse
from typing import Any, Union, TypeVar, Callable, Sequence, Dict
import jinja2
import voluptuous as vol
from homeassistant.loader import get_platform
@ -16,8 +15,10 @@ from homeassistant.const import (
CONF_CONDITION, CONF_BELOW, CONF_ABOVE, SUN_EVENT_SUNSET,
SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC)
from homeassistant.core import valid_entity_id
from homeassistant.exceptions import TemplateError
import homeassistant.util.dt as dt_util
from homeassistant.util import slugify
from homeassistant.helpers import template as template_helper
# pylint: disable=invalid-name
@ -103,6 +104,11 @@ def entity_ids(value: Union[str, Sequence]) -> Sequence[str]:
return [entity_id(ent_id) for ent_id in value]
def enum(enumClass):
"""Create validator for specified enum."""
return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__)
def icon(value):
"""Validate icon."""
value = str(value)
@ -234,14 +240,15 @@ def template(value):
"""Validate a jinja2 template."""
if value is None:
raise vol.Invalid('template value is None')
if isinstance(value, (list, dict)):
elif isinstance(value, (list, dict, template_helper.Template)):
raise vol.Invalid('template value should be a string')
value = str(value)
value = template_helper.Template(str(value))
try:
jinja2.Environment().parse(value)
value.ensure_valid()
return value
except jinja2.exceptions.TemplateSyntaxError as ex:
except TemplateError as ex:
raise vol.Invalid('invalid template ({})'.format(ex))

View File

@ -28,7 +28,7 @@ CONF_DELAY = "delay"
def call_from_config(hass: HomeAssistant, config: ConfigType,
variables: Optional[Sequence]=None) -> None:
"""Call a script based on a config entry."""
Script(hass, config).run(variables)
Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables)
class Script():
@ -39,7 +39,8 @@ class Script():
change_listener=None) -> None:
"""Initialize the script."""
self.hass = hass
self.sequence = cv.SCRIPT_SCHEMA(sequence)
self.sequence = sequence
template.attach(hass, self.sequence)
self.name = name
self._change_listener = change_listener
self._cur = -1
@ -48,6 +49,7 @@ class Script():
in self.sequence)
self._lock = threading.Lock()
self._unsub_delay_listener = None
self._template_cache = {}
@property
def is_running(self) -> bool:
@ -77,11 +79,11 @@ class Script():
delay = action[CONF_DELAY]
if isinstance(delay, str):
if isinstance(delay, template.Template):
delay = vol.All(
cv.time_period,
cv.positive_timedelta)(
template.render(self.hass, delay))
delay.render())
self._unsub_delay_listener = track_point_in_utc_time(
self.hass, script_delay,
@ -133,7 +135,7 @@ class Script():
def _check_condition(self, action, variables):
"""Test if condition is matching."""
self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION])
check = condition.from_config(action)(self.hass, variables)
check = condition.from_config(action, False)(self.hass, variables)
self._log("Test condition {}: {}".format(self.last_action, check))
return check

View File

@ -9,7 +9,6 @@ import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant # NOQA
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
@ -49,8 +48,8 @@ def call_from_config(hass, config, blocking=False, variables=None,
domain_service = config[CONF_SERVICE]
else:
try:
domain_service = template.render(
hass, config[CONF_SERVICE_TEMPLATE], variables)
config[CONF_SERVICE_TEMPLATE].hass = hass
domain_service = config[CONF_SERVICE_TEMPLATE].render(variables)
domain_service = cv.service(domain_service)
except TemplateError as ex:
_LOGGER.error('Error rendering service name template: %s', ex)
@ -73,7 +72,8 @@ def call_from_config(hass, config, blocking=False, variables=None,
for key, element in value.items():
value[key] = _data_template_creator(element)
return value
return template.render(hass, value, variables)
value.hass = hass
return value.render(variables)
if CONF_SERVICE_DATA_TEMPLATE in config:
for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items():

View File

@ -2,73 +2,162 @@
# pylint: disable=too-few-public-methods
import json
import logging
import re
import jinja2
from jinja2.sandbox import ImmutableSandboxedEnvironment
from homeassistant.const import STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE
from homeassistant.const import (
STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL)
from homeassistant.core import State
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import location as loc_helper
from homeassistant.loader import get_component
from homeassistant.util import convert, dt as dt_util, location as loc_util
from homeassistant.util.async import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
_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
})
_RE_NONE_ENTITIES = re.compile(r"distance\(|closest\(", re.I | re.M)
_RE_GET_ENTITIES = re.compile(
r"(?:(?:states\.|(?:is_state|is_state_attr|states)\(.)([\w]+\.[\w]+))",
re.I | re.M
)
def render_with_possible_json_value(hass, template, value,
error_value=_SENTINEL):
"""Render template with value exposed.
If valid JSON will expose value_json too.
"""
variables = {
'value': value
}
try:
variables['value_json'] = json.loads(value)
except ValueError:
pass
try:
return render(hass, template, variables)
except TemplateError as ex:
_LOGGER.error('Error parsing value: %s', ex)
return value if error_value is _SENTINEL else error_value
def attach(hass, obj):
"""Recursively attach hass to all template instances in list and dict."""
if isinstance(obj, list):
for child in obj:
attach(hass, child)
elif isinstance(obj, dict):
for child in obj.values():
attach(hass, child)
elif isinstance(obj, Template):
obj.hass = hass
def render(hass, template, variables=None, **kwargs):
"""Render given template."""
if variables is not None:
kwargs.update(variables)
def extract_entities(template):
"""Extract all entities for state_changed listener from template string."""
if template is None or _RE_NONE_ENTITIES.search(template):
return MATCH_ALL
try:
if not isinstance(template, jinja2.Template):
template = compile_template(hass, template)
extraction = _RE_GET_ENTITIES.findall(template)
if len(extraction) > 0:
return list(set(extraction))
return MATCH_ALL
return template.render(kwargs).strip()
except jinja2.TemplateError as err:
raise TemplateError(err)
class Template(object):
"""Class to hold a template and manage caching and rendering."""
def __init__(self, template, hass=None):
"""Instantiate a Template."""
if not isinstance(template, str):
raise TypeError('Expected template to be a string')
self.template = template
self._compiled_code = None
self._compiled = None
self.hass = hass
def ensure_valid(self):
"""Return if template is valid."""
if self._compiled_code is not None:
return
try:
self._compiled_code = ENV.compile(self.template)
except jinja2.exceptions.TemplateSyntaxError as err:
raise TemplateError(err)
def extract_entities(self):
"""Extract all entities for state_changed listener."""
return extract_entities(self.template)
def render(self, variables=None, **kwargs):
"""Render given template."""
if variables is not None:
kwargs.update(variables)
return run_callback_threadsafe(
self.hass.loop, self.async_render, kwargs).result()
def async_render(self, variables=None, **kwargs):
"""Render given template.
This method must be run in the event loop.
"""
self._ensure_compiled()
if variables is not None:
kwargs.update(variables)
try:
return self._compiled.render(kwargs).strip()
except jinja2.TemplateError as err:
raise TemplateError(err)
def render_with_possible_json_value(self, value, error_value=_SENTINEL):
"""Render template with value exposed.
If valid JSON will expose value_json too.
"""
return run_callback_threadsafe(
self.hass.loop, self.async_render_with_possible_json_value, value,
error_value).result()
# pylint: disable=invalid-name
def async_render_with_possible_json_value(self, value,
error_value=_SENTINEL):
"""Render template with value exposed.
If valid JSON will expose value_json too.
This method must be run in the event loop.
"""
self._ensure_compiled()
variables = {
'value': value
}
try:
variables['value_json'] = json.loads(value)
except ValueError:
pass
try:
return self._compiled.render(variables).strip()
except jinja2.TemplateError as ex:
_LOGGER.error('Error parsing value: %s (value: %s, template: %s)',
ex, value, self.template)
return value if error_value is _SENTINEL else error_value
def _ensure_compiled(self):
"""Bind a template to a specific hass instance."""
if self._compiled is not None:
return
self.ensure_valid()
assert self.hass is not None, 'hass variable not set on template'
location_methods = LocationMethods(self.hass)
global_vars = ENV.make_globals({
'closest': location_methods.closest,
'distance': location_methods.distance,
'is_state': self.hass.states.async_is_state,
'is_state_attr': self.hass.states.async_is_state_attr,
'states': AllStates(self.hass),
})
self._compiled = jinja2.Template.from_code(
ENV, self._compiled_code, global_vars, None)
return self._compiled
class AllStates(object):
@ -84,7 +173,7 @@ class AllStates(object):
def __iter__(self):
"""Return all states."""
return iter(sorted(self._hass.states.all(),
return iter(sorted(self._hass.states.async_all(),
key=lambda state: state.entity_id))
def __call__(self, entity_id):
@ -108,7 +197,7 @@ class DomainStates(object):
def __iter__(self):
"""Return the iteration over all the states."""
return iter(sorted(
(state for state in self._hass.states.all()
(state for state in self._hass.states.async_all()
if state.domain == self._domain),
key=lambda state: state.entity_id))
@ -313,3 +402,8 @@ ENV.filters['multiply'] = multiply
ENV.filters['timestamp_custom'] = timestamp_custom
ENV.filters['timestamp_local'] = timestamp_local
ENV.filters['timestamp_utc'] = timestamp_utc
ENV.globals['float'] = forgiving_float
ENV.globals['now'] = dt_util.now
ENV.globals['utcnow'] = dt_util.utcnow
ENV.globals['as_timestamp'] = dt_util.as_timestamp
ENV.globals['relative_time'] = dt_util.get_age

View File

@ -4,6 +4,7 @@ import unittest
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components.binary_sensor import command_line
from homeassistant import bootstrap
from homeassistant.helpers import template
from tests.common import get_test_home_assistant
@ -56,7 +57,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
entity = command_line.CommandBinarySensor(
self.hass, data, 'test', None, '1.0', '0',
'{{ value | multiply(0.1) }}')
template.Template('{{ value | multiply(0.1) }}', self.hass))
self.assertEqual(STATE_ON, entity.state)

View File

@ -4,8 +4,10 @@ from unittest import mock
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
import homeassistant.bootstrap as bootstrap
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor import template
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template as template_hlpr
from tests.common import get_test_home_assistant
@ -13,31 +15,39 @@ from tests.common import get_test_home_assistant
class TestBinarySensorTemplate(unittest.TestCase):
"""Test for Binary sensor template platform."""
def setup_method(self, method):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
"""Stop everything that was started."""
self.hass.stop()
@mock.patch.object(template, 'BinarySensorTemplate')
def test_setup(self, mock_template):
""""Test the setup."""
config = {
tpl = template_hlpr.Template('{{ foo }}', self.hass)
config = PLATFORM_SCHEMA({
'platform': 'template',
'sensors': {
'test': {
'friendly_name': 'virtual thingy',
'value_template': '{{ foo }}',
'value_template': tpl,
'sensor_class': 'motion',
'entity_id': 'test'
},
}
}
hass = mock.MagicMock()
})
add_devices = mock.MagicMock()
result = template.setup_platform(hass, config, add_devices)
result = template.setup_platform(self.hass, config, add_devices)
self.assertTrue(result)
mock_template.assert_called_once_with(hass, 'test', 'virtual thingy',
'motion', '{{ foo }}', 'test')
mock_template.assert_called_once_with(
self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test')
add_devices.assert_called_once_with([mock_template.return_value])
def test_setup_no_sensors(self):
""""Test setup with no sensors."""
hass = mock.MagicMock()
result = bootstrap.setup_component(hass, 'sensor', {
result = bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'template'
}
@ -46,8 +56,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_device(self):
""""Test the setup with invalid devices."""
hass = mock.MagicMock()
result = bootstrap.setup_component(hass, 'sensor', {
result = bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'template',
'sensors': {
@ -59,8 +68,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_sensor_class(self):
""""Test setup with invalid sensor class."""
hass = mock.MagicMock()
result = bootstrap.setup_component(hass, 'sensor', {
result = bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'template',
'sensors': {
@ -75,8 +83,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_missing_template(self):
""""Test setup with invalid and missing template."""
hass = mock.MagicMock()
result = bootstrap.setup_component(hass, 'sensor', {
result = bootstrap.setup_component(self.hass, 'sensor', {
'sensor': {
'platform': 'template',
'sensors': {
@ -90,9 +97,9 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_attributes(self):
""""Test the attributes."""
hass = mock.MagicMock()
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
'motion', '{{ 1 > 1 }}', MATCH_ALL)
vs = template.BinarySensorTemplate(
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
self.assertFalse(vs.should_poll)
self.assertEqual('motion', vs.sensor_class)
self.assertEqual('Parent', vs.name)
@ -100,32 +107,29 @@ class TestBinarySensorTemplate(unittest.TestCase):
vs.update()
self.assertFalse(vs.is_on)
vs._template = "{{ 2 > 1 }}"
vs._template = template_hlpr.Template("{{ 2 > 1 }}", self.hass)
vs.update()
self.assertTrue(vs.is_on)
def test_event(self):
""""Test the event."""
hass = get_test_home_assistant()
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
'motion', '{{ 1 > 1 }}', MATCH_ALL)
vs = template.BinarySensorTemplate(
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
vs.update_ha_state()
hass.block_till_done()
self.hass.block_till_done()
with mock.patch.object(vs, 'update') as mock_update:
hass.bus.fire(EVENT_STATE_CHANGED)
hass.block_till_done()
try:
assert mock_update.call_count == 1
finally:
hass.stop()
self.hass.bus.fire(EVENT_STATE_CHANGED)
self.hass.block_till_done()
assert mock_update.call_count == 1
@mock.patch('homeassistant.helpers.template.render')
@mock.patch('homeassistant.helpers.template.Template.render')
def test_update_template_error(self, mock_render):
""""Test the template update error."""
hass = mock.MagicMock()
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
'motion', '{{ 1 > 1 }}', MATCH_ALL)
vs = template.BinarySensorTemplate(
self.hass, 'parent', 'Parent', 'motion',
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
mock_render.side_effect = TemplateError('foo')
vs.update()
mock_render.side_effect = TemplateError(

View File

@ -118,20 +118,6 @@ class TestMQTT(unittest.TestCase):
}, blocking=True)
self.assertFalse(mqtt.MQTT_CLIENT.publish.called)
def test_service_call_without_payload_or_payload_template(self):
"""Test the service call without payload or payload template.
Send empty message if neither 'payload' nor 'payload_template'
are provided.
"""
# Call the service directly because the helper functions require you to
# provide a payload.
self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, {
mqtt.ATTR_TOPIC: "test/topic"
}, blocking=True)
self.assertTrue(mqtt.MQTT_CLIENT.publish.called)
self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "")
def test_service_call_with_ascii_qos_retain_flags(self):
"""Test the service call with args that can be misinterpreted.

View File

@ -1,6 +1,7 @@
"""The tests for the Command line sensor platform."""
import unittest
from homeassistant.helpers.template import Template
from homeassistant.components.sensor import command_line
from homeassistant import bootstrap
from tests.common import get_test_home_assistant
@ -53,7 +54,8 @@ class TestCommandSensorSensor(unittest.TestCase):
data = command_line.CommandSensorData('echo 50')
entity = command_line.CommandSensor(
self.hass, data, 'test', 'in', '{{ value | multiply(0.1) }}')
self.hass, data, 'test', 'in',
Template('{{ value | multiply(0.1) }}', self.hass))
self.assertEqual(5, float(entity.state))

View File

@ -1,16 +1,16 @@
"""The tests for the IMAP email content sensor platform."""
import unittest
from collections import deque
import email
import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import datetime
from threading import Event
import unittest
from homeassistant.helpers.template import Template
from homeassistant.helpers.event import track_state_change
from collections import deque
from homeassistant.components.sensor import imap_email_content
from tests.common import get_test_home_assistant
@ -218,7 +218,8 @@ class EmailContentSensor(unittest.TestCase):
FakeEMailReader(deque([test_message])),
"test_emails_sensor",
["sender@test.com"],
"{{ subject }} from {{ from }} with message {{ body }}")
Template("{{ subject }} from {{ from }} with message {{ body }}",
self.hass))
sensor.entity_id = "sensor.emailtest"
sensor.update()

View File

@ -3,11 +3,14 @@
import unittest
from unittest.mock import patch
import pytest
from homeassistant.bootstrap import _setup_component
from homeassistant.components import rfxtrx as rfxtrx
from tests.common import get_test_home_assistant
@pytest.mark.skipif("os.environ.get('RFXTRX') == 'SKIP'")
class TestRFXTRX(unittest.TestCase):
"""Test the Rfxtrx component."""

View File

@ -2,7 +2,7 @@
# pylint: disable=too-many-public-methods,protected-access
import unittest
from homeassistant.bootstrap import _setup_component
from homeassistant.bootstrap import setup_component
from homeassistant.components import script
from tests.common import get_test_home_assistant
@ -41,7 +41,7 @@ class TestScriptComponent(unittest.TestCase):
}
},
):
assert not _setup_component(self.hass, 'script', {
assert not setup_component(self.hass, 'script', {
'script': value
}), 'Script loaded with wrong config {}'.format(value)
@ -58,7 +58,7 @@ class TestScriptComponent(unittest.TestCase):
self.hass.bus.listen(event, record_event)
assert _setup_component(self.hass, 'script', {
assert setup_component(self.hass, 'script', {
'script': {
'test': {
'sequence': [{
@ -93,7 +93,7 @@ class TestScriptComponent(unittest.TestCase):
self.hass.bus.listen(event, record_event)
assert _setup_component(self.hass, 'script', {
assert setup_component(self.hass, 'script', {
'script': {
'test': {
'sequence': [{
@ -127,7 +127,7 @@ class TestScriptComponent(unittest.TestCase):
self.hass.services.register('test', 'script', record_call)
assert _setup_component(self.hass, 'script', {
assert setup_component(self.hass, 'script', {
'script': {
'test': {
'sequence': {

View File

@ -5,7 +5,7 @@ import unittest
from unittest.mock import patch
from subprocess import SubprocessError
from homeassistant.bootstrap import _setup_component
from homeassistant.bootstrap import setup_component
from homeassistant.components import shell_command
from tests.common import get_test_home_assistant
@ -26,7 +26,7 @@ class TestShellCommand(unittest.TestCase):
"""Test if able to call a configured service."""
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
assert _setup_component(self.hass, shell_command.DOMAIN, {
assert setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "date > {}".format(path)
}
@ -40,41 +40,54 @@ class TestShellCommand(unittest.TestCase):
def test_config_not_dict(self):
"""Test if config is not a dict."""
assert not _setup_component(self.hass, shell_command.DOMAIN, {
assert not setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: ['some', 'weird', 'list']
})
def test_config_not_valid_service_names(self):
"""Test if config contains invalid service names."""
assert not _setup_component(self.hass, shell_command.DOMAIN, {
assert not setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'this is invalid because space': 'touch bla.txt'
}
})
def test_template_render_no_template(self):
@patch('homeassistant.components.shell_command.subprocess.call')
def test_template_render_no_template(self, mock_call):
"""Ensure shell_commands without templates get rendered properly."""
cmd, shell = shell_command._parse_command(self.hass, 'ls /bin', {})
self.assertTrue(shell)
self.assertEqual(cmd, 'ls /bin')
assert setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "ls /bin"
}
})
def test_template_render(self):
"""Ensure shell_commands with templates get rendered properly."""
self.hass.services.call('shell_command', 'test_service',
blocking=True)
cmd = mock_call.mock_calls[0][1][0]
shell = mock_call.mock_calls[0][2]['shell']
assert 'ls /bin' == cmd
assert shell
@patch('homeassistant.components.shell_command.subprocess.call')
def test_template_render(self, mock_call):
"""Ensure shell_commands without templates get rendered properly."""
self.hass.states.set('sensor.test_state', 'Works')
cmd, shell = shell_command._parse_command(
self.hass,
'ls /bin {{ states.sensor.test_state.state }}', {}
)
self.assertFalse(shell, False)
self.assertEqual(cmd[-1], 'Works')
assert setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "ls /bin {{ states.sensor.test_state.state }}"
}
})
def test_invalid_template_fails(self):
"""Test that shell_commands with invalid templates fail."""
cmd, _shell = shell_command._parse_command(
self.hass,
'ls /bin {{ states. .test_state.state }}', {}
)
self.assertEqual(cmd, None)
self.hass.services.call('shell_command', 'test_service',
blocking=True)
cmd = mock_call.mock_calls[0][1][0]
shell = mock_call.mock_calls[0][2]['shell']
assert ['ls', '/bin', 'Works'] == cmd
assert not shell
@patch('homeassistant.components.shell_command.subprocess.call',
side_effect=SubprocessError)
@ -83,7 +96,7 @@ class TestShellCommand(unittest.TestCase):
"""Test subprocess."""
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
assert _setup_component(self.hass, shell_command.DOMAIN, {
assert setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "touch {}".format(path)
}

View File

@ -44,6 +44,32 @@ class TestConditionHelper:
self.hass.states.set('sensor.temperature', 100)
assert test(self.hass)
def test_and_condition_with_template(self):
"""Test the 'and' condition."""
test = condition.from_config({
'condition': 'and',
'conditions': [
{
'condition': 'template',
'value_template':
'{{ states.sensor.temperature.state == "100" }}',
}, {
'condition': 'numeric_state',
'entity_id': 'sensor.temperature',
'below': 110,
}
]
})
self.hass.states.set('sensor.temperature', 120)
assert not test(self.hass)
self.hass.states.set('sensor.temperature', 105)
assert not test(self.hass)
self.hass.states.set('sensor.temperature', 100)
assert test(self.hass)
def test_or_condition(self):
"""Test the 'or' condition."""
test = condition.from_config({
@ -70,6 +96,32 @@ class TestConditionHelper:
self.hass.states.set('sensor.temperature', 100)
assert test(self.hass)
def test_or_condition_with_template(self):
"""Test the 'or' condition."""
test = condition.from_config({
'condition': 'or',
'conditions': [
{
'condition': 'template',
'value_template':
'{{ states.sensor.temperature.state == "100" }}',
}, {
'condition': 'numeric_state',
'entity_id': 'sensor.temperature',
'below': 110,
}
]
})
self.hass.states.set('sensor.temperature', 120)
assert not test(self.hass)
self.hass.states.set('sensor.temperature', 105)
assert test(self.hass)
self.hass.states.set('sensor.temperature', 100)
assert test(self.hass)
def test_time_window(self):
"""Test time condition windows."""
sixam = dt.parse_time("06:00:00")

View File

@ -1,6 +1,7 @@
"""Test config validators."""
from collections import OrderedDict
from datetime import timedelta
import enum
import os
import tempfile
@ -302,7 +303,8 @@ def test_template():
schema = vol.Schema(cv.template)
for value in (None, '{{ partial_print }', '{% if True %}Hello', ['test']):
with pytest.raises(vol.MultipleInvalid):
with pytest.raises(vol.Invalid,
message='{} not considered invalid'.format(value)):
schema(value)
for value in (
@ -417,3 +419,19 @@ def test_ordered_dict_value_validator():
schema({'hello': 'world'})
schema({'hello': 5})
def test_enum():
"""Test enum validator."""
class TestEnum(enum.Enum):
"""Test enum."""
value1 = "Value 1"
value2 = "Value 2"
schema = vol.Schema(cv.enum(TestEnum))
with pytest.raises(vol.Invalid):
schema('value3')
TestEnum['value1']

View File

@ -6,7 +6,7 @@ import unittest
# Otherwise can't test just this file (import order issue)
import homeassistant.components # noqa
import homeassistant.util.dt as dt_util
from homeassistant.helpers import script
from homeassistant.helpers import script, config_validation as cv
from tests.common import fire_time_changed, get_test_home_assistant
@ -36,12 +36,12 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event)
script_obj = script.Script(self.hass, {
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA({
'event': event,
'event_data': {
'hello': 'world'
}
})
}))
script_obj.run()
@ -61,14 +61,13 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call)
script_obj = script.Script(self.hass, {
script.call_from_config(self.hass, {
'service': 'test.script',
'data': {
'hello': 'world'
}
})
script_obj.run()
self.hass.block_till_done()
assert len(calls) == 1
@ -84,7 +83,7 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call)
script_obj = script.Script(self.hass, {
script.call_from_config(self.hass, {
'service_template': """
{% if True %}
test.script
@ -102,8 +101,6 @@ class TestScriptHelper(unittest.TestCase):
}
})
script_obj.run()
self.hass.block_till_done()
assert len(calls) == 1
@ -120,10 +117,10 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event)
script_obj = script.Script(self.hass, [
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'event': event},
{'delay': {'seconds': 5}},
{'event': event}])
{'event': event}]))
script_obj.run()
@ -152,10 +149,10 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event)
script_obj = script.Script(self.hass, [
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'event': event},
{'delay': '00:00:{{ 5 }}'},
{'event': event}])
{'event': event}]))
script_obj.run()
@ -184,9 +181,9 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event)
script_obj = script.Script(self.hass, [
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'delay': {'seconds': 5}},
{'event': event}])
{'event': event}]))
script_obj.run()
@ -217,7 +214,7 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call)
script_obj = script.Script(self.hass, [
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{
'service': 'test.script',
'data_template': {
@ -230,7 +227,7 @@ class TestScriptHelper(unittest.TestCase):
'data_template': {
'hello': '{{ greeting2 }}',
},
}])
}]))
script_obj.run({
'greeting': 'world',
@ -264,14 +261,14 @@ class TestScriptHelper(unittest.TestCase):
self.hass.states.set('test.entity', 'hello')
script_obj = script.Script(self.hass, [
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'event': event},
{
'condition': 'template',
'value_template': '{{ states.test.entity.state == "hello" }}',
},
{'event': event},
])
]))
script_obj.run()
self.hass.block_till_done()

View File

@ -12,13 +12,14 @@ from homeassistant.const import (
TEMP_CELSIUS,
MASS_GRAMS,
VOLUME_LITERS,
MATCH_ALL,
)
import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant
class TestUtilTemplate(unittest.TestCase):
class TestHelpersTemplate(unittest.TestCase):
"""Test the Template."""
def setUp(self): # pylint: disable=invalid-name
@ -37,7 +38,8 @@ class TestUtilTemplate(unittest.TestCase):
self.hass.states.set('test.object', 'happy')
self.assertEqual(
'happy',
template.render(self.hass, '{{ states.test.object.state }}'))
template.Template(
'{{ states.test.object.state }}', self.hass).render())
def test_iterating_all_states(self):
"""Test iterating all states."""
@ -46,9 +48,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'10happy',
template.render(
self.hass,
'{% for state in states %}{{ state.state }}{% endfor %}'))
template.Template(
'{% for state in states %}{{ state.state }}{% endfor %}',
self.hass).render())
def test_iterating_domain_states(self):
"""Test iterating domain states."""
@ -58,11 +60,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'open10',
template.render(
self.hass,
"""
template.Template("""
{% for state in states.sensor %}{{ state.state }}{% endfor %}
"""))
""", self.hass).render())
def test_float(self):
"""Test float."""
@ -70,15 +70,15 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'12.0',
template.render(
self.hass,
'{{ float(states.sensor.temperature.state) }}'))
template.Template(
'{{ float(states.sensor.temperature.state) }}',
self.hass).render())
self.assertEqual(
'True',
template.render(
self.hass,
'{{ float(states.sensor.temperature.state) > 11 }}'))
template.Template(
'{{ float(states.sensor.temperature.state) > 11 }}',
self.hass).render())
def test_rounding_value(self):
"""Test rounding value."""
@ -86,32 +86,26 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'12.8',
template.render(
self.hass,
'{{ states.sensor.temperature.state | round(1) }}'))
template.Template(
'{{ states.sensor.temperature.state | round(1) }}',
self.hass).render())
self.assertEqual(
'128',
template.render(
self.hass,
'{{ states.sensor.temperature.state | multiply(10) | round }}'
))
template.Template(
'{{ states.sensor.temperature.state | multiply(10) | round }}',
self.hass).render())
def test_rounding_value_get_original_value_on_error(self):
"""Test rounding value get original value on error."""
self.assertEqual(
'None',
template.render(
self.hass,
'{{ None | round }}'
))
template.Template('{{ None | round }}', self.hass).render())
self.assertEqual(
'no_number',
template.render(
self.hass,
'{{ "no_number" | round }}'
))
template.Template(
'{{ "no_number" | round }}', self.hass).render())
def test_multiply(self):
"""Test multiply."""
@ -124,8 +118,8 @@ class TestUtilTemplate(unittest.TestCase):
for inp, out in tests.items():
self.assertEqual(
out,
template.render(self.hass,
'{{ %s | multiply(10) | round }}' % inp))
template.Template('{{ %s | multiply(10) | round }}' % inp,
self.hass).render())
def test_timestamp_custom(self):
"""Test the timestamps to custom filter."""
@ -148,8 +142,8 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
out,
template.render(self.hass, '{{ %s | %s }}' % (inp, fil))
)
template.Template('{{ %s | %s }}' % (inp, fil),
self.hass).render())
def test_timestamp_local(self):
"""Test the timestamps to local filter."""
@ -161,8 +155,8 @@ class TestUtilTemplate(unittest.TestCase):
for inp, out in tests.items():
self.assertEqual(
out,
template.render(self.hass,
'{{ %s | timestamp_local }}' % inp))
template.Template('{{ %s | timestamp_local }}' % inp,
self.hass).render())
def test_timestamp_utc(self):
"""Test the timestamps to local filter."""
@ -176,112 +170,101 @@ class TestUtilTemplate(unittest.TestCase):
for inp, out in tests.items():
self.assertEqual(
out,
template.render(self.hass,
'{{ %s | timestamp_utc }}' % inp))
template.Template('{{ %s | timestamp_utc }}' % inp,
self.hass).render())
def test_passing_vars_as_keywords(self):
"""Test passing variables as keywords."""
self.assertEqual(
'127', template.render(self.hass, '{{ hello }}', hello=127))
'127',
template.Template('{{ hello }}', self.hass).render(hello=127))
def test_passing_vars_as_vars(self):
"""Test passing variables as variables."""
self.assertEqual(
'127', template.render(self.hass, '{{ hello }}', {'hello': 127}))
'127',
template.Template('{{ hello }}', self.hass).render({'hello': 127}))
def test_render_with_possible_json_value_with_valid_json(self):
"""Render with possible JSON value with valid JSON."""
tpl = template.Template('{{ value_json.hello }}', self.hass)
self.assertEqual(
'world',
template.render_with_possible_json_value(
self.hass, '{{ value_json.hello }}', '{"hello": "world"}'))
tpl.render_with_possible_json_value('{"hello": "world"}'))
def test_render_with_possible_json_value_with_invalid_json(self):
"""Render with possible JSON value with invalid JSON."""
tpl = template.Template('{{ value_json }}', self.hass)
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'))
tpl.render_with_possible_json_value('{ I AM NOT JSON }'))
def test_render_with_possible_json_value_with_template_error_value(self):
"""Render with possible JSON value with template error value."""
tpl = template.Template('{{ non_existing.variable }}', self.hass)
self.assertEqual(
'-',
template.render_with_possible_json_value(
self.hass, '{{ value_json', 'hello', '-'))
tpl.render_with_possible_json_value('hello', '-'))
def test_raise_exception_on_error(self):
"""Test raising an exception on error."""
with self.assertRaises(TemplateError):
template.render(self.hass, '{{ invalid_syntax')
template.Template('{{ invalid_syntax').ensure_valid()
def test_if_state_exists(self):
"""Test if state exists works."""
self.hass.states.set('test.object', 'available')
self.assertEqual(
'exists',
template.render(
self.hass,
"""
{% if states.test.object %}exists{% else %}not exists{% endif %}
"""))
tpl = template.Template(
'{% if states.test.object %}exists{% else %}not exists{% endif %}',
self.hass)
self.assertEqual('exists', tpl.render())
def test_is_state(self):
"""Test is_state method."""
self.hass.states.set('test.object', 'available')
self.assertEqual(
'yes',
template.render(
self.hass,
"""
tpl = template.Template("""
{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}
"""))
""", self.hass)
self.assertEqual('yes', tpl.render())
def test_is_state_attr(self):
"""Test is_state_attr method."""
self.hass.states.set('test.object', 'available', {'mode': 'on'})
self.assertEqual(
'yes',
template.render(
self.hass,
"""
tpl = template.Template("""
{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %}
"""))
""", self.hass)
self.assertEqual('yes', tpl.render())
def test_states_function(self):
"""Test using states as a function."""
self.hass.states.set('test.object', 'available')
self.assertEqual(
'available',
template.render(self.hass, '{{ states("test.object") }}'))
self.assertEqual(
'unknown',
template.render(self.hass, '{{ states("test.object2") }}'))
tpl = template.Template('{{ states("test.object") }}', self.hass)
self.assertEqual('available', tpl.render())
tpl2 = template.Template('{{ states("test.object2") }}', self.hass)
self.assertEqual('unknown', tpl2.render())
@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):
def test_now(self, mock_is_safe):
"""Test now method."""
self.assertEqual(
dt_util.now().isoformat(),
template.render(self.hass, '{{ now().isoformat() }}'))
now = dt_util.now()
with patch.dict(template.ENV.globals, {'now': lambda: now}):
self.assertEqual(
now.isoformat(),
template.Template('{{ now().isoformat() }}',
self.hass).render())
@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):
def test_utcnow(self, mock_is_safe):
"""Test utcnow method."""
self.assertEqual(
dt_util.utcnow().isoformat(),
template.render(self.hass, '{{ utcnow().isoformat() }}'))
now = dt_util.utcnow()
with patch.dict(template.ENV.globals, {'utcnow': lambda: now}):
self.assertEqual(
now.isoformat(),
template.Template('{{ utcnow().isoformat() }}',
self.hass).render())
def test_distance_function_with_1_state(self):
"""Test distance function with 1 state."""
@ -289,11 +272,9 @@ class TestUtilTemplate(unittest.TestCase):
'latitude': 32.87336,
'longitude': -117.22943,
})
self.assertEqual(
'187',
template.render(
self.hass, '{{ distance(states.test.object) | round }}'))
tpl = template.Template('{{ distance(states.test.object) | round }}',
self.hass)
self.assertEqual('187', tpl.render())
def test_distance_function_with_2_states(self):
"""Test distance function with 2 states."""
@ -301,34 +282,31 @@ class TestUtilTemplate(unittest.TestCase):
'latitude': 32.87336,
'longitude': -117.22943,
})
self.hass.states.set('test.object_2', 'happy', {
'latitude': self.hass.config.latitude,
'longitude': self.hass.config.longitude,
})
self.assertEqual(
'187',
template.render(
self.hass,
'{{ distance(states.test.object, states.test.object_2)'
'| round }}'))
tpl = template.Template(
'{{ distance(states.test.object, states.test.object_2) | round }}',
self.hass)
self.assertEqual('187', tpl.render())
def test_distance_function_with_1_coord(self):
"""Test distance function with 1 coord."""
tpl = template.Template(
'{{ distance("32.87336", "-117.22943") | round }}', self.hass)
self.assertEqual(
'187',
template.render(
self.hass, '{{ distance("32.87336", "-117.22943") | round }}'))
tpl.render())
def test_distance_function_with_2_coords(self):
"""Test distance function with 2 coords."""
self.assertEqual(
'187',
template.render(
self.hass,
template.Template(
'{{ distance("32.87336", "-117.22943", %s, %s) | round }}'
% (self.hass.config.latitude, self.hass.config.longitude)))
% (self.hass.config.latitude, self.hass.config.longitude),
self.hass).render())
def test_distance_function_with_1_state_1_coord(self):
"""Test distance function with 1 state 1 coord."""
@ -336,57 +314,47 @@ class TestUtilTemplate(unittest.TestCase):
'latitude': self.hass.config.latitude,
'longitude': self.hass.config.longitude,
})
tpl = template.Template(
'{{ distance("32.87336", "-117.22943", states.test.object_2) '
'| round }}', self.hass)
self.assertEqual('187', tpl.render())
self.assertEqual(
'187',
template.render(
self.hass,
'{{ distance("32.87336", "-117.22943", states.test.object_2) '
'| round }}'))
self.assertEqual(
'187',
template.render(
self.hass,
'{{ distance(states.test.object_2, "32.87336", "-117.22943") '
'| round }}'))
tpl2 = template.Template(
'{{ distance(states.test.object_2, "32.87336", "-117.22943") '
'| round }}', self.hass)
self.assertEqual('187', tpl2.render())
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,
})
tpl = template.Template('{{ distance(states.test.object_2) | round }}',
self.hass)
self.assertEqual(
'None',
template.render(
self.hass,
'{{ distance(states.test.object_2) | round }}'))
tpl.render())
def test_distance_function_return_None_if_invalid_coord(self):
"""Test distance function return None if invalid coord."""
self.assertEqual(
'None',
template.render(
self.hass,
'{{ distance("123", "abc") }}'))
template.Template(
'{{ distance("123", "abc") }}', self.hass).render())
self.assertEqual(
'None',
template.render(
self.hass,
'{{ distance("123") }}'))
template.Template('{{ distance("123") }}', self.hass).render())
self.hass.states.set('test.object_2', 'happy', {
'latitude': self.hass.config.latitude,
'longitude': self.hass.config.longitude,
})
tpl = template.Template('{{ distance("123", states.test_object_2) }}',
self.hass)
self.assertEqual(
'None',
template.render(
self.hass,
'{{ distance("123", states.test_object_2) }}'))
tpl.render())
def test_closest_function_home_vs_domain(self):
"""Test closest function home vs domain."""
@ -402,8 +370,8 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain.object',
template.render(self.hass,
'{{ closest(states.test_domain).entity_id }}'))
template.Template('{{ closest(states.test_domain).entity_id }}',
self.hass).render())
def test_closest_function_home_vs_all_states(self):
"""Test closest function home vs all states."""
@ -419,8 +387,8 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain_2.and_closer',
template.render(self.hass,
'{{ closest(states).entity_id }}'))
template.Template('{{ closest(states).entity_id }}',
self.hass).render())
def test_closest_function_home_vs_group_entity_id(self):
"""Test closest function home vs group entity id."""
@ -438,8 +406,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain.object',
template.render(self.hass,
'{{ closest("group.location_group").entity_id }}'))
template.Template(
'{{ closest("group.location_group").entity_id }}',
self.hass).render())
def test_closest_function_home_vs_group_state(self):
"""Test closest function home vs group state."""
@ -457,9 +426,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain.object',
template.render(
self.hass,
'{{ closest(states.group.location_group).entity_id }}'))
template.Template(
'{{ closest(states.group.location_group).entity_id }}',
self.hass).render())
def test_closest_function_to_coord(self):
"""Test closest function to coord."""
@ -478,14 +447,14 @@ class TestUtilTemplate(unittest.TestCase):
'longitude': self.hass.config.longitude + 0.3,
})
tpl = template.Template(
'{{ closest("%s", %s, states.test_domain).entity_id }}'
% (self.hass.config.latitude + 0.3,
self.hass.config.longitude + 0.3), self.hass)
self.assertEqual(
'test_domain.closest_zone',
template.render(
self.hass,
'{{ closest("%s", %s, states.test_domain).entity_id }}'
% (self.hass.config.latitude + 0.3,
self.hass.config.longitude + 0.3))
)
tpl.render())
def test_closest_function_to_entity_id(self):
"""Test closest function to entity id."""
@ -506,10 +475,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain.closest_zone',
template.render(
self.hass,
'{{ closest("zone.far_away", states.test_domain).entity_id }}')
)
template.Template(
'{{ closest("zone.far_away", '
'states.test_domain).entity_id }}', self.hass).render())
def test_closest_function_to_state(self):
"""Test closest function to state."""
@ -530,11 +498,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'test_domain.closest_zone',
template.render(
self.hass,
template.Template(
'{{ closest(states.zone.far_away, '
'states.test_domain).entity_id }}')
)
'states.test_domain).entity_id }}', self.hass).render())
def test_closest_function_invalid_state(self):
"""Test closest function invalid state."""
@ -546,8 +512,8 @@ class TestUtilTemplate(unittest.TestCase):
for state in ('states.zone.non_existing', '"zone.non_existing"'):
self.assertEqual(
'None',
template.render(
self.hass, '{{ closest(%s, states) }}' % state))
template.Template('{{ closest(%s, states) }}' % state,
self.hass).render())
def test_closest_function_state_with_invalid_location(self):
"""Test closest function state with invalid location."""
@ -558,10 +524,9 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'None',
template.render(
self.hass,
template.Template(
'{{ closest(states.test_domain.closest_home, '
'states) }}'))
'states) }}', self.hass).render())
def test_closest_function_invalid_coordinates(self):
"""Test closest function invalid coordinates."""
@ -572,20 +537,96 @@ class TestUtilTemplate(unittest.TestCase):
self.assertEqual(
'None',
template.render(self.hass,
'{{ closest("invalid", "coord", states) }}'))
template.Template('{{ closest("invalid", "coord", states) }}',
self.hass).render())
def test_closest_function_no_location_states(self):
"""Test closest function without location states."""
self.assertEqual('None',
template.render(self.hass, '{{ closest(states) }}'))
self.assertEqual(
'None',
template.Template('{{ closest(states) }}', self.hass).render())
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 }}')
def test_extract_entities_none_exclude_stuff(self):
"""Test extract entities function with none or exclude stuff."""
self.assertEqual(MATCH_ALL, template.extract_entities(None))
with patch('homeassistant.helpers.template.compile_template',
side_effect=Exception('Should not be called')):
assert 'world' == template.render(self.hass, compiled)
self.assertEqual(
MATCH_ALL,
template.extract_entities(
'{{ closest(states.zone.far_away, '
'states.test_domain).entity_id }}'))
self.assertEqual(
MATCH_ALL,
template.extract_entities(
'{{ distance("123", states.test_object_2) }}'))
def test_extract_entities_no_match_entities(self):
"""Test extract entities function with none entities stuff."""
self.assertEqual(
MATCH_ALL,
template.extract_entities(
"{{ value_json.tst | timestamp_custom('%Y' True) }}"))
self.assertEqual(
MATCH_ALL,
template.extract_entities("""
{% for state in states.sensor %}
{{ state.entity_id }}={{ state.state }},
{% endfor %}
"""))
def test_extract_entities_match_entities(self):
"""Test extract entities function with entities stuff."""
self.assertListEqual(
['device_tracker.phone_1'],
template.extract_entities("""
{% if is_state('device_tracker.phone_1', 'home') %}
Ha, Hercules is home!
{% else %}
Hercules is at {{ states('device_tracker.phone_1') }}.
{% endif %}
"""))
self.assertListEqual(
['binary_sensor.garage_door'],
template.extract_entities("""
{{ as_timestamp(states.binary_sensor.garage_door.last_changed) }}
"""))
self.assertListEqual(
['binary_sensor.garage_door'],
template.extract_entities("""
{{ states("binary_sensor.garage_door") }}
"""))
self.assertListEqual(
['device_tracker.phone_2'],
template.extract_entities("""
is_state_attr('device_tracker.phone_2', 'battery', 40)
"""))
self.assertListEqual(
sorted([
'device_tracker.phone_1',
'device_tracker.phone_2',
]),
sorted(template.extract_entities("""
{% if is_state('device_tracker.phone_1', 'home') %}
Ha, Hercules is home!
{% elif states.device_tracker.phone_2.attributes.battery < 40 %}
Hercules you power goes done!.
{% endif %}
""")))
self.assertListEqual(
sorted([
'sensor.pick_humidity',
'sensor.pick_temperature',
]),
sorted(template.extract_entities("""
{{
states.sensor.pick_temperature.state ~ °C ( ~
states.sensor.pick_humidity.state ~ %
}}
""")))