Optimize template 2 (#3521)

* 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
This commit is contained in:
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 For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/ https://home-assistant.io/components/alexa/
""" """
import copy
import enum import enum
import logging import logging
import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST 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 from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,10 +23,49 @@ CONF_CARD = 'card'
CONF_INTENTS = 'intents' CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech' CONF_SPEECH = 'speech'
CONF_TYPE = 'type'
CONF_TITLE = 'title'
CONF_CONTENT = 'content'
CONF_TEXT = 'text'
DOMAIN = 'alexa' DOMAIN = 'alexa'
DEPENDENCIES = ['http'] 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): def setup(hass, config):
"""Activate Alexa component.""" """Activate Alexa component."""
hass.wsgi.register_view(AlexaView(hass, hass.wsgi.register_view(AlexaView(hass,
@ -42,6 +84,9 @@ class AlexaView(HomeAssistantView):
"""Initialize Alexa view.""" """Initialize Alexa view."""
super().__init__(hass) super().__init__(hass)
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items(): for name, intent in intents.items():
if CONF_ACTION in intent: if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script( intent[CONF_ACTION] = script.Script(
@ -101,29 +146,15 @@ class AlexaView(HomeAssistantView):
# pylint: disable=unsubscriptable-object # pylint: disable=unsubscriptable-object
if speech is not None: 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: if card is not None:
response.add_card(CardType[card['type']], card['title'], response.add_card(card[CONF_TYPE], card[CONF_TITLE],
card['content']) card[CONF_CONTENT])
return self.json(response) 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): class AlexaResponse(object):
"""Help generating the response for Alexa.""" """Help generating the response for Alexa."""
@ -153,8 +184,8 @@ class AlexaResponse(object):
self.card = card self.card = card
return return
card["title"] = self._render(title), card["title"] = title.render(self.variables)
card["content"] = self._render(content) card["content"] = content.render(self.variables)
self.card = card self.card = card
def add_speech(self, speech_type, text): def add_speech(self, speech_type, text):
@ -163,9 +194,12 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text' key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.render(self.variables)
self.speech = { self.speech = {
'type': speech_type.value, 'type': speech_type.value,
key: self._render(text) key: text
} }
def add_reprompt(self, speech_type, text): def add_reprompt(self, speech_type, text):
@ -176,7 +210,7 @@ class AlexaResponse(object):
self.reprompt = { self.reprompt = {
'type': speech_type.value, 'type': speech_type.value,
key: self._render(text) key: text.render(self.variables)
} }
def as_dict(self): def as_dict(self):
@ -201,7 +235,3 @@ class AlexaResponse(object):
'sessionAttributes': self.session_attributes, 'sessionAttributes': self.session_attributes,
'response': response, '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): def post(self, request):
"""Render a template.""" """Render a template."""
try: try:
return template.render(self.hass, request.json['template'], tpl = template.Template(request.json['template'], self.hass)
request.json.get('variables')) return tpl.render(request.json.get('variables'))
except TemplateError as ex: except TemplateError as ex:
return self.json_message('Error rendering template: {}'.format(ex), return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST) HTTP_BAD_REQUEST)

View file

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

View file

@ -4,13 +4,13 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger at https://home-assistant.io/components/automation/#template-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL) from homeassistant.helpers import condition
from homeassistant.helpers import condition, template
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -25,21 +25,22 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
def trigger(hass, config, action): def trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
value_template = template.compile_template( value_template = config.get(CONF_VALUE_TEMPLATE)
hass, config.get(CONF_VALUE_TEMPLATE)) value_template.hass = hass
# Local variable to keep track of if the action has already been triggered # Local variable to keep track of if the action has already been triggered
already_triggered = False already_triggered = False
@asyncio.coroutine
def state_changed_listener(entity_id, from_s, to_s): def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action.""" """Listen for state changes and calls action."""
nonlocal already_triggered 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 # Check to see if template returns true
if template_result and not already_triggered: if template_result and not already_triggered:
already_triggered = True already_triggered = True
action({ hass.async_add_job(action, {
'trigger': { 'trigger': {
'platform': 'template', 'platform': 'template',
'entity_id': entity_id, 'entity_id': entity_id,
@ -50,4 +51,5 @@ def trigger(hass, config, action):
elif not template_result: elif not template_result:
already_triggered = False 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 ( from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND) CONF_SENSOR_CLASS, CONF_COMMAND)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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) payload_on = config.get(CONF_PAYLOAD_ON)
sensor_class = config.get(CONF_SENSOR_CLASS) sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None: if value_template is not None:
value_template = template.compile_template(hass, value_template) value_template.hass = hass
data = CommandSensorData(command) data = CommandSensorData(command)
add_devices([CommandBinarySensor( add_devices([CommandBinarySensor(
@ -94,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
value = self.data.value value = self.data.value
if self._value_template is not None: if self._value_template is not None:
value = template.render_with_possible_json_value( value = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, value, False) value, False)
if value == self._payload_on: if value == self._payload_on:
self._state = True self._state = True
elif value == self._payload_off: 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_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS) CONF_SENSOR_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS) from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor.""" """Setup the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None: if value_template is not None:
value_template = template.compile_template(hass, value_template) value_template.hass = hass
add_devices([MqttBinarySensor( add_devices([MqttBinarySensor(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
@ -73,8 +70,8 @@ class MqttBinarySensor(BinarySensorDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._payload_on: if payload == self._payload_on:
self._state = True self._state = True
self.update_ha_state() self.update_ha_state()

View file

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

View file

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

View file

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

View file

@ -15,7 +15,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) 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__) _LOGGER = logging.getLogger(__name__)
@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
DEFAULT_NAME = 'Generic Camera' DEFAULT_NAME = 'Generic Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 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.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
@ -50,8 +50,8 @@ class GenericCamera(Camera):
super().__init__() super().__init__()
self.hass = hass self.hass = hass
self._name = device_info.get(CONF_NAME) self._name = device_info.get(CONF_NAME)
self._still_image_url = template.compile_template( self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
hass, device_info[CONF_STILL_IMAGE_URL]) self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME) username = device_info.get(CONF_USERNAME)
@ -71,7 +71,7 @@ class GenericCamera(Camera):
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try: try:
url = template.render(self.hass, self._still_image_url) url = self._still_image_url.render()
except TemplateError as err: except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s', _LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err) 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_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME) CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__) _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(): for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE) value_template = device_config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
if value_template is not None:
value_template = template.compile_template(hass, value_template)
covers.append( covers.append(
CommandCover( CommandCover(
@ -141,8 +138,8 @@ class CommandCover(CoverDevice):
if self._command_state: if self._command_state:
payload = str(self._query_state()) payload = str(self._query_state())
if self._value_template: if self._value_template:
payload = template.render_with_possible_json_value( payload = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, payload) payload)
self._state = int(payload) self._state = int(payload)
def open_cover(self, **kwargs): def open_cover(self, **kwargs):

View file

@ -15,7 +15,6 @@ from homeassistant.const import (
STATE_CLOSED) STATE_CLOSED)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT Cover.""" """Setup the MQTT Cover."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None: if value_template is not None:
value_template = template.compile_template(hass, value_template) value_template.hass = hass
add_devices([MqttCover( add_devices([MqttCover(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
@ -96,8 +93,8 @@ class MqttCover(CoverDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._state_open: if payload == self._state_open:
self._state = False self._state = False
_LOGGER.warning("state=%s", int(self._state)) _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/ https://home-assistant.io/components/fan.mqtt/
""" """
import logging import logging
from functools import partial
import voluptuous as vol import voluptuous as vol
@ -16,7 +15,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv 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, from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
SPEED_HIGH, FanEntity, SPEED_HIGH, FanEntity,
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
@ -139,9 +137,12 @@ class MqttFan(FanEntity):
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC] self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED) is not None and SUPPORT_SET_SPEED)
templates = {key: ((lambda value: value) if tpl is None else for key, tpl in list(templates.items()):
partial(render_with_possible_json_value, hass, tpl)) if tpl is None:
for key, tpl in templates.items()} templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
def state_received(topic, payload, qos): def state_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""

View file

@ -16,7 +16,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__) _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): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Garage Door.""" """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( add_devices_callback([MqttGarageDoor(
hass, hass,
config[CONF_NAME], 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_OPEN],
config[CONF_SERVICE_CLOSE], config[CONF_SERVICE_CLOSE],
config[CONF_OPTIMISTIC], config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))]) value_template)])
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
@ -84,8 +86,8 @@ class MqttGarageDoor(GarageDoorDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._state_open: if payload == self._state_open:
self._state = True self._state = True
self.update_ha_state() 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/ https://home-assistant.io/components/light.mqtt/
""" """
import logging import logging
from functools import partial
import voluptuous as vol import voluptuous as vol
@ -19,7 +18,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -117,9 +115,12 @@ class MqttLight(Light):
topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and
SUPPORT_BRIGHTNESS) SUPPORT_BRIGHTNESS)
templates = {key: ((lambda value: value) if tpl is None else for key, tpl in list(templates.items()):
partial(render_with_possible_json_value, hass, tpl)) if tpl is None:
for key, tpl in templates.items()} templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
def state_received(topic, payload, qos): def state_received(topic, payload, qos):
"""A new MQTT message has been received.""" """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) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE) CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
from homeassistant.helpers import template
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -42,6 +41,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT lock.""" """Setup the MQTT lock."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttLock( add_devices([MqttLock(
hass, hass,
config.get(CONF_NAME), 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_LOCK),
config.get(CONF_PAYLOAD_UNLOCK), config.get(CONF_PAYLOAD_UNLOCK),
config.get(CONF_OPTIMISTIC), config.get(CONF_OPTIMISTIC),
config.get(CONF_VALUE_TEMPLATE) value_template,
)]) )])
@ -77,8 +79,8 @@ class MqttLock(LockDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._payload_lock: if payload == self._payload_lock:
self._state = True self._state = True
self.update_ha_state() self.update_ha_state()

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ import subprocess
from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.components.rollershutter import RollershutterDevice
from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template from homeassistant.helpers.template import Template
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,6 +20,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
devices = [] devices = []
for dev_name, properties in rollershutters.items(): 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( devices.append(
CommandRollershutter( CommandRollershutter(
hass, hass,
@ -28,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
properties.get('downcmd', 'true'), properties.get('downcmd', 'true'),
properties.get('stopcmd', 'true'), properties.get('stopcmd', 'true'),
properties.get('statecmd', False), properties.get('statecmd', False),
properties.get(CONF_VALUE_TEMPLATE, '{{ value }}'))) value_template))
add_devices_callback(devices) add_devices_callback(devices)
@ -103,8 +108,8 @@ class CommandRollershutter(RollershutterDevice):
if self._command_state: if self._command_state:
payload = str(self._query_state()) payload = str(self._query_state())
if self._value_template: if self._value_template:
payload = template.render_with_possible_json_value( payload = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, payload) payload)
self._state = int(payload) self._state = int(payload)
def move_up(self, **kwargs): 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.const import CONF_NAME, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Rollershutter.""" """Add MQTT Rollershutter."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices_callback([MqttRollershutter( add_devices_callback([MqttRollershutter(
hass, hass,
config[CONF_NAME], 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_UP],
config[CONF_PAYLOAD_DOWN], config[CONF_PAYLOAD_DOWN],
config[CONF_PAYLOAD_STOP], config[CONF_PAYLOAD_STOP],
config.get(CONF_VALUE_TEMPLATE) value_template,
)]) )])
@ -76,8 +78,8 @@ class MqttRollershutter(RollershutterDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload.isnumeric() and 0 <= int(payload) <= 100: if payload.isnumeric() and 0 <= int(payload) <= 100:
self._state = int(payload) self._state = int(payload)
self.update_ha_state() 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: if value_template is None:
return lambda value: value return lambda value: value
value_template = template.Template(value_template, hass)
def _render(value): def _render(value):
try: try:
return template.render(hass, value_template, {'value': value}) return value_template.render({'value': value})
except TemplateError: except TemplateError:
_LOGGER.exception('Error parsing value') _LOGGER.exception('Error parsing value')
return value return value

View file

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

View file

@ -15,7 +15,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT) CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['dweepy==0.2.0'] REQUIREMENTS = ['dweepy==0.2.0']
@ -45,16 +44,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device = config.get(CONF_DEVICE) device = config.get(CONF_DEVICE)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
unit = config.get(CONF_UNIT_OF_MEASUREMENT) unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template.hass = hass
try: try:
content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content']) content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content'])
except dweepy.DweepyError: except dweepy.DweepyError:
_LOGGER.error("Device/thing '%s' could not be found", device) _LOGGER.error("Device/thing '%s' could not be found", device)
return False return False
if template.render_with_possible_json_value(hass, if value_template.render_with_possible_json_value(content) == '':
value_template,
content) is '':
_LOGGER.error("'%s' was not found", value_template) _LOGGER.error("'%s' was not found", value_template)
return False return False
@ -94,8 +91,8 @@ class DweetSensor(Entity):
return STATE_UNKNOWN return STATE_UNKNOWN
else: else:
values = json.dumps(self.dweet.data[0]['content']) values = json.dumps(self.dweet.data[0]['content'])
value = template.render_with_possible_json_value( value = self._value_template.render_with_possible_json_value(
self.hass, self._value_template, values) values)
return value return value
def update(self): 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) sensor_names = config.get(CONF_SENSOR_NAMES)
interval = config.get(CONF_SCAN_INTERVAL) interval = config.get(CONF_SCAN_INTERVAL)
if value_template is not None:
value_template.hass = hass
data = EmonCmsData(hass, url, apikey, interval) data = EmonCmsData(hass, url, apikey, interval)
data.update() data.update()
@ -123,9 +126,8 @@ class EmonCmsSensor(Entity):
self._elem = elem self._elem = elem
if self._value_template is not None: if self._value_template is not None:
self._state = template.render_with_possible_json_value( self._state = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, elem["value"], elem["value"], STATE_UNKNOWN)
STATE_UNKNOWN)
else: else:
self._state = round(float(elem["value"]), DECIMALS) self._state = round(float(elem["value"]), DECIMALS)
@ -177,9 +179,8 @@ class EmonCmsSensor(Entity):
self._elem = elem self._elem = elem
if self._value_template is not None: if self._value_template is not None:
self._state = template.render_with_possible_json_value( self._state = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, elem["value"], elem["value"], STATE_UNKNOWN)
STATE_UNKNOWN)
else: else:
self._state = round(float(elem["value"]), DECIMALS) 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.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ( 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 import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
import voluptuous as vol import voluptuous as vol
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_SERVER = "server" CONF_SERVER = "server"
CONF_SENDERS = "senders" CONF_SENDERS = "senders"
CONF_VALUE_TEMPLATE = "value_template"
ATTR_FROM = "from" ATTR_FROM = "from"
ATTR_BODY = "body" ATTR_BODY = "body"
@ -48,12 +46,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_SERVER), config.get(CONF_SERVER),
config.get(CONF_PORT)) config.get(CONF_PORT))
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
sensor = EmailContentSensor( sensor = EmailContentSensor(
hass, hass,
reader, reader,
config.get(CONF_NAME, None) or config.get(CONF_USERNAME), config.get(CONF_NAME, None) or config.get(CONF_USERNAME),
config.get(CONF_SENDERS), config.get(CONF_SENDERS),
config.get(CONF_VALUE_TEMPLATE)) value_template)
if sensor.connected: if sensor.connected:
add_devices([sensor]) add_devices([sensor])
@ -172,7 +173,7 @@ class EmailContentSensor(Entity):
ATTR_DATE: email_message['Date'], ATTR_DATE: email_message['Date'],
ATTR_BODY: EmailContentSensor.get_msg_text(email_message) 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): def sender_allowed(self, email_message):
"""Check if the sender is in the allowed senders list.""" """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.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT) CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
from homeassistant.helpers import template
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -30,13 +29,16 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup MQTT Sensor.""" """Setup MQTT Sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttSensor( add_devices([MqttSensor(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC), config.get(CONF_STATE_TOPIC),
config.get(CONF_QOS), config.get(CONF_QOS),
config.get(CONF_UNIT_OF_MEASUREMENT), 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): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
self._state = payload self._state = payload
self.update_ha_state() 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_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, CONF_VERIFY_SSL) CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, CONF_VERIFY_SSL)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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) verify_ssl = config.get(CONF_VERIFY_SSL)
unit = config.get(CONF_UNIT_OF_MEASUREMENT) unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE) 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 = RestData(method, resource, payload, verify_ssl)
rest.update() rest.update()
@ -92,8 +92,8 @@ class RestSensor(Entity):
if value is None: if value is None:
value = STATE_UNKNOWN value = STATE_UNKNOWN
elif self._value_template is not None: elif self._value_template is not None:
value = template.render_with_possible_json_value( value = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, value, STATE_UNKNOWN) value, STATE_UNKNOWN)
self._state = value self._state = value

View file

@ -9,9 +9,9 @@ import socket
import select import select
from homeassistant.const import CONF_NAME, CONF_HOST from homeassistant.const import CONF_NAME, CONF_HOST
from homeassistant.helpers import template
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
CONF_PORT = "port" CONF_PORT = "port"
CONF_TIMEOUT = "timeout" CONF_TIMEOUT = "timeout"
@ -41,6 +41,11 @@ class Sensor(Entity):
def __init__(self, hass, config): def __init__(self, hass, config):
"""Set all the config values if they exist and get initial state.""" """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._hass = hass
self._config = { self._config = {
CONF_NAME: config.get(CONF_NAME), CONF_NAME: config.get(CONF_NAME),
@ -49,7 +54,7 @@ class Sensor(Entity):
CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
CONF_PAYLOAD: config[CONF_PAYLOAD], CONF_PAYLOAD: config[CONF_PAYLOAD],
CONF_UNIT: config.get(CONF_UNIT), 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_VALUE_ON: config.get(CONF_VALUE_ON),
CONF_BUFFER_SIZE: config.get( CONF_BUFFER_SIZE: config.get(
CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE),
@ -122,9 +127,7 @@ class Sensor(Entity):
if self._config[CONF_VALUE_TEMPLATE] is not None: if self._config[CONF_VALUE_TEMPLATE] is not None:
try: try:
self._state = template.render( self._state = self._config[CONF_VALUE_TEMPLATE].render(
self._hass,
self._config[CONF_VALUE_TEMPLATE],
value=value) value=value)
return return
except TemplateError as err: 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.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, 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.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import Entity, generate_entity_id from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -24,7 +23,7 @@ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): 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({ 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(): for device, device_config in config[CONF_SENSORS].items():
state_template = device_config[CONF_VALUE_TEMPLATE] 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) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT) unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
state_template.hass = hass
sensors.append( sensors.append(
SensorTemplate( SensorTemplate(
hass, hass,
@ -71,7 +73,7 @@ class SensorTemplate(Entity):
hass=hass) hass=hass)
self._name = friendly_name self._name = friendly_name
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._template = template.compile_template(hass, state_template) self._template = state_template
self._state = None self._state = None
self.update() self.update()
@ -105,7 +107,7 @@ class SensorTemplate(Entity):
def update(self): def update(self):
"""Get the latest data and update the states.""" """Get the latest data and update the states."""
try: try:
self._state = template.render(self.hass, self._template) self._state = self._template.render()
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):

View file

@ -29,12 +29,41 @@ def setup(hass, config):
"""Setup the shell_command component.""" """Setup the shell_command component."""
conf = config.get(DOMAIN, {}) conf = config.get(DOMAIN, {})
cache = {}
def service_handler(call): def service_handler(call):
"""Execute a shell command service.""" """Execute a shell command service."""
cmd = conf[call.service] cmd = conf[call.service]
cmd, shell = _parse_command(hass, cmd, call.data)
if cmd is None: 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 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: try:
subprocess.call(cmd, shell=shell, subprocess.call(cmd, shell=shell,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
@ -45,23 +74,3 @@ def setup(hass, config):
for name in conf.keys(): for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler) hass.services.register(DOMAIN, name, service_handler)
return True 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 ( from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF, CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF,
CONF_COMMAND_ON, CONF_COMMAND_STATE) CONF_COMMAND_ON, CONF_COMMAND_STATE)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,6 +37,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
switches = [] switches = []
for device_name, device_config in devices.items(): for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
switches.append( switches.append(
CommandSwitch( CommandSwitch(
hass, 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_ON),
device_config.get(CONF_COMMAND_OFF), device_config.get(CONF_COMMAND_OFF),
device_config.get(CONF_COMMAND_STATE), 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: if self._command_state:
payload = str(self._query_state()) payload = str(self._query_state())
if self._value_template: if self._value_template:
payload = template.render_with_possible_json_value( payload = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, payload) payload)
self._state = (payload.lower() == "true") self._state = (payload.lower() == "true")
def turn_on(self, **kwargs): def turn_on(self, **kwargs):

View file

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

View file

@ -12,9 +12,8 @@ from homeassistant.components.switch import (
ENTITY_ID_FORMAT, SwitchDevice, PLATFORM_SCHEMA) ENTITY_ID_FORMAT, SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON, 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.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
@ -31,7 +30,7 @@ SWITCH_SCHEMA = vol.Schema({
vol.Required(ON_ACTION): cv.SCRIPT_SCHEMA, vol.Required(ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA, vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string, 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({ 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] state_template = device_config[CONF_VALUE_TEMPLATE]
on_action = device_config[ON_ACTION] on_action = device_config[ON_ACTION]
off_action = device_config[OFF_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( switches.append(
SwitchTemplate( SwitchTemplate(
@ -79,7 +79,8 @@ class SwitchTemplate(SwitchDevice):
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass) hass=hass)
self._name = friendly_name self._name = friendly_name
self._template = template.compile_template(hass, state_template) self._template = state_template
state_template.hass = hass
self._on_script = Script(hass, on_action) self._on_script = Script(hass, on_action)
self._off_script = Script(hass, off_action) self._off_script = Script(hass, off_action)
self._state = False self._state = False
@ -123,7 +124,7 @@ class SwitchTemplate(SwitchDevice):
def update(self): def update(self):
"""Update the state from the template.""" """Update the state from the template."""
try: try:
state = template.render(self.hass, self._template).lower() state = self._template.render().lower()
if state in _VALID_STATES: if state in _VALID_STATES:
self._state = state in ('true', STATE_ON) self._state = state in ('true', STATE_ON)

View file

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

View file

@ -6,7 +6,6 @@ from urllib.parse import urlparse
from typing import Any, Union, TypeVar, Callable, Sequence, Dict from typing import Any, Union, TypeVar, Callable, Sequence, Dict
import jinja2
import voluptuous as vol import voluptuous as vol
from homeassistant.loader import get_platform from homeassistant.loader import get_platform
@ -16,8 +15,10 @@ from homeassistant.const import (
CONF_CONDITION, CONF_BELOW, CONF_ABOVE, SUN_EVENT_SUNSET, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, SUN_EVENT_SUNSET,
SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC) SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC)
from homeassistant.core import valid_entity_id from homeassistant.core import valid_entity_id
from homeassistant.exceptions import TemplateError
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.util import slugify from homeassistant.util import slugify
from homeassistant.helpers import template as template_helper
# pylint: disable=invalid-name # 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] 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): def icon(value):
"""Validate icon.""" """Validate icon."""
value = str(value) value = str(value)
@ -234,14 +240,15 @@ def template(value):
"""Validate a jinja2 template.""" """Validate a jinja2 template."""
if value is None: if value is None:
raise vol.Invalid('template 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') raise vol.Invalid('template value should be a string')
value = str(value) value = template_helper.Template(str(value))
try: try:
jinja2.Environment().parse(value) value.ensure_valid()
return value return value
except jinja2.exceptions.TemplateSyntaxError as ex: except TemplateError as ex:
raise vol.Invalid('invalid template ({})'.format(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, def call_from_config(hass: HomeAssistant, config: ConfigType,
variables: Optional[Sequence]=None) -> None: variables: Optional[Sequence]=None) -> None:
"""Call a script based on a config entry.""" """Call a script based on a config entry."""
Script(hass, config).run(variables) Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables)
class Script(): class Script():
@ -39,7 +39,8 @@ class Script():
change_listener=None) -> None: change_listener=None) -> None:
"""Initialize the script.""" """Initialize the script."""
self.hass = hass self.hass = hass
self.sequence = cv.SCRIPT_SCHEMA(sequence) self.sequence = sequence
template.attach(hass, self.sequence)
self.name = name self.name = name
self._change_listener = change_listener self._change_listener = change_listener
self._cur = -1 self._cur = -1
@ -48,6 +49,7 @@ class Script():
in self.sequence) in self.sequence)
self._lock = threading.Lock() self._lock = threading.Lock()
self._unsub_delay_listener = None self._unsub_delay_listener = None
self._template_cache = {}
@property @property
def is_running(self) -> bool: def is_running(self) -> bool:
@ -77,11 +79,11 @@ class Script():
delay = action[CONF_DELAY] delay = action[CONF_DELAY]
if isinstance(delay, str): if isinstance(delay, template.Template):
delay = vol.All( delay = vol.All(
cv.time_period, cv.time_period,
cv.positive_timedelta)( cv.positive_timedelta)(
template.render(self.hass, delay)) delay.render())
self._unsub_delay_listener = track_point_in_utc_time( self._unsub_delay_listener = track_point_in_utc_time(
self.hass, script_delay, self.hass, script_delay,
@ -133,7 +135,7 @@ class Script():
def _check_condition(self, action, variables): def _check_condition(self, action, variables):
"""Test if condition is matching.""" """Test if condition is matching."""
self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION]) 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)) self._log("Test condition {}: {}".format(self.last_action, check))
return check return check

View file

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

View file

@ -2,46 +2,124 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import json import json
import logging import logging
import re
import jinja2 import jinja2
from jinja2.sandbox import ImmutableSandboxedEnvironment 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.core import State
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers import location as loc_helper from homeassistant.helpers import location as loc_helper
from homeassistant.loader import get_component from homeassistant.loader import get_component
from homeassistant.util import convert, dt as dt_util, location as loc_util 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__) _LOGGER = logging.getLogger(__name__)
_SENTINEL = object() _SENTINEL = object()
DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S" DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
_RE_NONE_ENTITIES = re.compile(r"distance\(|closest\(", re.I | re.M)
def compile_template(hass, template): _RE_GET_ENTITIES = re.compile(
"""Compile a template.""" r"(?:(?:states\.|(?:is_state|is_state_attr|states)\(.)([\w]+\.[\w]+))",
location_methods = LocationMethods(hass) re.I | re.M
)
return ENV.from_string(template, {
'closest': location_methods.closest,
'distance': location_methods.distance,
'float': forgiving_float,
'is_state': hass.states.is_state,
'is_state_attr': hass.states.is_state_attr,
'now': dt_util.now,
'states': AllStates(hass),
'utcnow': dt_util.utcnow,
'as_timestamp': dt_util.as_timestamp,
'relative_time': dt_util.get_age
})
def render_with_possible_json_value(hass, template, value, def attach(hass, obj):
error_value=_SENTINEL): """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 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
extraction = _RE_GET_ENTITIES.findall(template)
if len(extraction) > 0:
return list(set(extraction))
return MATCH_ALL
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. """Render template with value exposed.
If valid JSON will expose value_json too. 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 = { variables = {
'value': value 'value': value
} }
@ -51,24 +129,35 @@ def render_with_possible_json_value(hass, template, value,
pass pass
try: try:
return render(hass, template, variables) return self._compiled.render(variables).strip()
except TemplateError as ex: except jinja2.TemplateError as ex:
_LOGGER.error('Error parsing value: %s', 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 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
def render(hass, template, variables=None, **kwargs): self.ensure_valid()
"""Render given template."""
if variables is not None:
kwargs.update(variables)
try: assert self.hass is not None, 'hass variable not set on template'
if not isinstance(template, jinja2.Template):
template = compile_template(hass, template)
return template.render(kwargs).strip() location_methods = LocationMethods(self.hass)
except jinja2.TemplateError as err:
raise TemplateError(err) 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): class AllStates(object):
@ -84,7 +173,7 @@ class AllStates(object):
def __iter__(self): def __iter__(self):
"""Return all states.""" """Return all states."""
return iter(sorted(self._hass.states.all(), return iter(sorted(self._hass.states.async_all(),
key=lambda state: state.entity_id)) key=lambda state: state.entity_id))
def __call__(self, entity_id): def __call__(self, entity_id):
@ -108,7 +197,7 @@ class DomainStates(object):
def __iter__(self): def __iter__(self):
"""Return the iteration over all the states.""" """Return the iteration over all the states."""
return iter(sorted( 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), if state.domain == self._domain),
key=lambda state: state.entity_id)) key=lambda state: state.entity_id))
@ -313,3 +402,8 @@ ENV.filters['multiply'] = multiply
ENV.filters['timestamp_custom'] = timestamp_custom ENV.filters['timestamp_custom'] = timestamp_custom
ENV.filters['timestamp_local'] = timestamp_local ENV.filters['timestamp_local'] = timestamp_local
ENV.filters['timestamp_utc'] = timestamp_utc 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.const import (STATE_ON, STATE_OFF)
from homeassistant.components.binary_sensor import command_line from homeassistant.components.binary_sensor import command_line
from homeassistant import bootstrap from homeassistant import bootstrap
from homeassistant.helpers import template
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
@ -56,7 +57,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
entity = command_line.CommandBinarySensor( entity = command_line.CommandBinarySensor(
self.hass, data, 'test', None, '1.0', '0', 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) 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 from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor import template from homeassistant.components.binary_sensor import template
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template as template_hlpr
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
@ -13,31 +15,39 @@ from tests.common import get_test_home_assistant
class TestBinarySensorTemplate(unittest.TestCase): class TestBinarySensorTemplate(unittest.TestCase):
"""Test for Binary sensor template platform.""" """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') @mock.patch.object(template, 'BinarySensorTemplate')
def test_setup(self, mock_template): def test_setup(self, mock_template):
""""Test the setup.""" """"Test the setup."""
config = { tpl = template_hlpr.Template('{{ foo }}', self.hass)
config = PLATFORM_SCHEMA({
'platform': 'template',
'sensors': { 'sensors': {
'test': { 'test': {
'friendly_name': 'virtual thingy', 'friendly_name': 'virtual thingy',
'value_template': '{{ foo }}', 'value_template': tpl,
'sensor_class': 'motion', 'sensor_class': 'motion',
'entity_id': 'test' 'entity_id': 'test'
}, },
} }
} })
hass = mock.MagicMock()
add_devices = 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) self.assertTrue(result)
mock_template.assert_called_once_with(hass, 'test', 'virtual thingy', mock_template.assert_called_once_with(
'motion', '{{ foo }}', 'test') self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test')
add_devices.assert_called_once_with([mock_template.return_value]) add_devices.assert_called_once_with([mock_template.return_value])
def test_setup_no_sensors(self): def test_setup_no_sensors(self):
""""Test setup with no sensors.""" """"Test setup with no sensors."""
hass = mock.MagicMock() result = bootstrap.setup_component(self.hass, 'sensor', {
result = bootstrap.setup_component(hass, 'sensor', {
'sensor': { 'sensor': {
'platform': 'template' 'platform': 'template'
} }
@ -46,8 +56,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_device(self): def test_setup_invalid_device(self):
""""Test the setup with invalid devices.""" """"Test the setup with invalid devices."""
hass = mock.MagicMock() result = bootstrap.setup_component(self.hass, 'sensor', {
result = bootstrap.setup_component(hass, 'sensor', {
'sensor': { 'sensor': {
'platform': 'template', 'platform': 'template',
'sensors': { 'sensors': {
@ -59,8 +68,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_sensor_class(self): def test_setup_invalid_sensor_class(self):
""""Test setup with invalid sensor class.""" """"Test setup with invalid sensor class."""
hass = mock.MagicMock() result = bootstrap.setup_component(self.hass, 'sensor', {
result = bootstrap.setup_component(hass, 'sensor', {
'sensor': { 'sensor': {
'platform': 'template', 'platform': 'template',
'sensors': { 'sensors': {
@ -75,8 +83,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_setup_invalid_missing_template(self): def test_setup_invalid_missing_template(self):
""""Test setup with invalid and missing template.""" """"Test setup with invalid and missing template."""
hass = mock.MagicMock() result = bootstrap.setup_component(self.hass, 'sensor', {
result = bootstrap.setup_component(hass, 'sensor', {
'sensor': { 'sensor': {
'platform': 'template', 'platform': 'template',
'sensors': { 'sensors': {
@ -90,9 +97,9 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_attributes(self): def test_attributes(self):
""""Test the attributes.""" """"Test the attributes."""
hass = mock.MagicMock() vs = template.BinarySensorTemplate(
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', self.hass, 'parent', 'Parent', 'motion',
'motion', '{{ 1 > 1 }}', MATCH_ALL) template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
self.assertFalse(vs.should_poll) self.assertFalse(vs.should_poll)
self.assertEqual('motion', vs.sensor_class) self.assertEqual('motion', vs.sensor_class)
self.assertEqual('Parent', vs.name) self.assertEqual('Parent', vs.name)
@ -100,32 +107,29 @@ class TestBinarySensorTemplate(unittest.TestCase):
vs.update() vs.update()
self.assertFalse(vs.is_on) self.assertFalse(vs.is_on)
vs._template = "{{ 2 > 1 }}" vs._template = template_hlpr.Template("{{ 2 > 1 }}", self.hass)
vs.update() vs.update()
self.assertTrue(vs.is_on) self.assertTrue(vs.is_on)
def test_event(self): def test_event(self):
""""Test the event.""" """"Test the event."""
hass = get_test_home_assistant() vs = template.BinarySensorTemplate(
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', self.hass, 'parent', 'Parent', 'motion',
'motion', '{{ 1 > 1 }}', MATCH_ALL) template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
vs.update_ha_state() vs.update_ha_state()
hass.block_till_done() self.hass.block_till_done()
with mock.patch.object(vs, 'update') as mock_update: with mock.patch.object(vs, 'update') as mock_update:
hass.bus.fire(EVENT_STATE_CHANGED) self.hass.bus.fire(EVENT_STATE_CHANGED)
hass.block_till_done() self.hass.block_till_done()
try:
assert mock_update.call_count == 1 assert mock_update.call_count == 1
finally:
hass.stop()
@mock.patch('homeassistant.helpers.template.render') @mock.patch('homeassistant.helpers.template.Template.render')
def test_update_template_error(self, mock_render): def test_update_template_error(self, mock_render):
""""Test the template update error.""" """"Test the template update error."""
hass = mock.MagicMock() vs = template.BinarySensorTemplate(
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', self.hass, 'parent', 'Parent', 'motion',
'motion', '{{ 1 > 1 }}', MATCH_ALL) template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
mock_render.side_effect = TemplateError('foo') mock_render.side_effect = TemplateError('foo')
vs.update() vs.update()
mock_render.side_effect = TemplateError( mock_render.side_effect = TemplateError(

View file

@ -118,20 +118,6 @@ class TestMQTT(unittest.TestCase):
}, blocking=True) }, blocking=True)
self.assertFalse(mqtt.MQTT_CLIENT.publish.called) 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): def test_service_call_with_ascii_qos_retain_flags(self):
"""Test the service call with args that can be misinterpreted. """Test the service call with args that can be misinterpreted.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,6 +44,32 @@ class TestConditionHelper:
self.hass.states.set('sensor.temperature', 100) self.hass.states.set('sensor.temperature', 100)
assert test(self.hass) 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): def test_or_condition(self):
"""Test the 'or' condition.""" """Test the 'or' condition."""
test = condition.from_config({ test = condition.from_config({
@ -70,6 +96,32 @@ class TestConditionHelper:
self.hass.states.set('sensor.temperature', 100) self.hass.states.set('sensor.temperature', 100)
assert test(self.hass) 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): def test_time_window(self):
"""Test time condition windows.""" """Test time condition windows."""
sixam = dt.parse_time("06:00:00") sixam = dt.parse_time("06:00:00")

View file

@ -1,6 +1,7 @@
"""Test config validators.""" """Test config validators."""
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import enum
import os import os
import tempfile import tempfile
@ -302,7 +303,8 @@ def test_template():
schema = vol.Schema(cv.template) schema = vol.Schema(cv.template)
for value in (None, '{{ partial_print }', '{% if True %}Hello', ['test']): 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) schema(value)
for value in ( for value in (
@ -417,3 +419,19 @@ def test_ordered_dict_value_validator():
schema({'hello': 'world'}) schema({'hello': 'world'})
schema({'hello': 5}) 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) # Otherwise can't test just this file (import order issue)
import homeassistant.components # noqa import homeassistant.components # noqa
import homeassistant.util.dt as dt_util 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 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) 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': event,
'event_data': { 'event_data': {
'hello': 'world' 'hello': 'world'
} }
}) }))
script_obj.run() script_obj.run()
@ -61,14 +61,13 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call) self.hass.services.register('test', 'script', record_call)
script_obj = script.Script(self.hass, { script.call_from_config(self.hass, {
'service': 'test.script', 'service': 'test.script',
'data': { 'data': {
'hello': 'world' 'hello': 'world'
} }
}) })
script_obj.run()
self.hass.block_till_done() self.hass.block_till_done()
assert len(calls) == 1 assert len(calls) == 1
@ -84,7 +83,7 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call) self.hass.services.register('test', 'script', record_call)
script_obj = script.Script(self.hass, { script.call_from_config(self.hass, {
'service_template': """ 'service_template': """
{% if True %} {% if True %}
test.script test.script
@ -102,8 +101,6 @@ class TestScriptHelper(unittest.TestCase):
} }
}) })
script_obj.run()
self.hass.block_till_done() self.hass.block_till_done()
assert len(calls) == 1 assert len(calls) == 1
@ -120,10 +117,10 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event) 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': event},
{'delay': {'seconds': 5}}, {'delay': {'seconds': 5}},
{'event': event}]) {'event': event}]))
script_obj.run() script_obj.run()
@ -152,10 +149,10 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event) 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': event},
{'delay': '00:00:{{ 5 }}'}, {'delay': '00:00:{{ 5 }}'},
{'event': event}]) {'event': event}]))
script_obj.run() script_obj.run()
@ -184,9 +181,9 @@ class TestScriptHelper(unittest.TestCase):
self.hass.bus.listen(event, record_event) 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}}, {'delay': {'seconds': 5}},
{'event': event}]) {'event': event}]))
script_obj.run() script_obj.run()
@ -217,7 +214,7 @@ class TestScriptHelper(unittest.TestCase):
self.hass.services.register('test', 'script', record_call) 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', 'service': 'test.script',
'data_template': { 'data_template': {
@ -230,7 +227,7 @@ class TestScriptHelper(unittest.TestCase):
'data_template': { 'data_template': {
'hello': '{{ greeting2 }}', 'hello': '{{ greeting2 }}',
}, },
}]) }]))
script_obj.run({ script_obj.run({
'greeting': 'world', 'greeting': 'world',
@ -264,14 +261,14 @@ class TestScriptHelper(unittest.TestCase):
self.hass.states.set('test.entity', 'hello') self.hass.states.set('test.entity', 'hello')
script_obj = script.Script(self.hass, [ script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'event': event}, {'event': event},
{ {
'condition': 'template', 'condition': 'template',
'value_template': '{{ states.test.entity.state == "hello" }}', 'value_template': '{{ states.test.entity.state == "hello" }}',
}, },
{'event': event}, {'event': event},
]) ]))
script_obj.run() script_obj.run()
self.hass.block_till_done() self.hass.block_till_done()

View file

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