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 asyncpull/3561/head
parent
6694b0470e
commit
00e298206e
homeassistant
components
automation
binary_sensor
camera
fan
garage_door
light
lock
mqtt
notify
rollershutter
|
@ -4,11 +4,14 @@ Support for Alexa skill service end point.
|
|||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import copy
|
||||
import enum
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import HTTP_BAD_REQUEST
|
||||
from homeassistant.helpers import template, script
|
||||
from homeassistant.helpers import template, script, config_validation as cv
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -20,10 +23,49 @@ CONF_CARD = 'card'
|
|||
CONF_INTENTS = 'intents'
|
||||
CONF_SPEECH = 'speech'
|
||||
|
||||
CONF_TYPE = 'type'
|
||||
CONF_TITLE = 'title'
|
||||
CONF_CONTENT = 'content'
|
||||
CONF_TEXT = 'text'
|
||||
|
||||
DOMAIN = 'alexa'
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
|
||||
class SpeechType(enum.Enum):
|
||||
"""The Alexa speech types."""
|
||||
|
||||
plaintext = "PlainText"
|
||||
ssml = "SSML"
|
||||
|
||||
|
||||
class CardType(enum.Enum):
|
||||
"""The Alexa card types."""
|
||||
|
||||
simple = "Simple"
|
||||
link_account = "LinkAccount"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
CONF_INTENTS: {
|
||||
cv.string: {
|
||||
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_CARD): {
|
||||
vol.Required(CONF_TYPE): cv.enum(CardType),
|
||||
vol.Required(CONF_TITLE): cv.template,
|
||||
vol.Required(CONF_CONTENT): cv.template,
|
||||
},
|
||||
vol.Optional(CONF_SPEECH): {
|
||||
vol.Required(CONF_TYPE): cv.enum(SpeechType),
|
||||
vol.Required(CONF_TEXT): cv.template,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Activate Alexa component."""
|
||||
hass.wsgi.register_view(AlexaView(hass,
|
||||
|
@ -42,6 +84,9 @@ class AlexaView(HomeAssistantView):
|
|||
"""Initialize Alexa view."""
|
||||
super().__init__(hass)
|
||||
|
||||
intents = copy.deepcopy(intents)
|
||||
template.attach(hass, intents)
|
||||
|
||||
for name, intent in intents.items():
|
||||
if CONF_ACTION in intent:
|
||||
intent[CONF_ACTION] = script.Script(
|
||||
|
@ -101,29 +146,15 @@ class AlexaView(HomeAssistantView):
|
|||
|
||||
# pylint: disable=unsubscriptable-object
|
||||
if speech is not None:
|
||||
response.add_speech(SpeechType[speech['type']], speech['text'])
|
||||
response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
|
||||
|
||||
if card is not None:
|
||||
response.add_card(CardType[card['type']], card['title'],
|
||||
card['content'])
|
||||
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
|
||||
card[CONF_CONTENT])
|
||||
|
||||
return self.json(response)
|
||||
|
||||
|
||||
class SpeechType(enum.Enum):
|
||||
"""The Alexa speech types."""
|
||||
|
||||
plaintext = "PlainText"
|
||||
ssml = "SSML"
|
||||
|
||||
|
||||
class CardType(enum.Enum):
|
||||
"""The Alexa card types."""
|
||||
|
||||
simple = "Simple"
|
||||
link_account = "LinkAccount"
|
||||
|
||||
|
||||
class AlexaResponse(object):
|
||||
"""Help generating the response for Alexa."""
|
||||
|
||||
|
@ -153,8 +184,8 @@ class AlexaResponse(object):
|
|||
self.card = card
|
||||
return
|
||||
|
||||
card["title"] = self._render(title),
|
||||
card["content"] = self._render(content)
|
||||
card["title"] = title.render(self.variables)
|
||||
card["content"] = content.render(self.variables)
|
||||
self.card = card
|
||||
|
||||
def add_speech(self, speech_type, text):
|
||||
|
@ -163,9 +194,12 @@ class AlexaResponse(object):
|
|||
|
||||
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
|
||||
|
||||
if isinstance(text, template.Template):
|
||||
text = text.render(self.variables)
|
||||
|
||||
self.speech = {
|
||||
'type': speech_type.value,
|
||||
key: self._render(text)
|
||||
key: text
|
||||
}
|
||||
|
||||
def add_reprompt(self, speech_type, text):
|
||||
|
@ -176,7 +210,7 @@ class AlexaResponse(object):
|
|||
|
||||
self.reprompt = {
|
||||
'type': speech_type.value,
|
||||
key: self._render(text)
|
||||
key: text.render(self.variables)
|
||||
}
|
||||
|
||||
def as_dict(self):
|
||||
|
@ -201,7 +235,3 @@ class AlexaResponse(object):
|
|||
'sessionAttributes': self.session_attributes,
|
||||
'response': response,
|
||||
}
|
||||
|
||||
def _render(self, template_string):
|
||||
"""Render a response, adding data from intent if available."""
|
||||
return template.render(self.hass, template_string, self.variables)
|
||||
|
|
|
@ -378,8 +378,8 @@ class APITemplateView(HomeAssistantView):
|
|||
def post(self, request):
|
||||
"""Render a template."""
|
||||
try:
|
||||
return template.render(self.hass, request.json['template'],
|
||||
request.json.get('variables'))
|
||||
tpl = template.Template(request.json['template'], self.hass)
|
||||
return tpl.render(request.json.get('variables'))
|
||||
except TemplateError as ex:
|
||||
return self.json_message('Error rendering template: {}'.format(ex),
|
||||
HTTP_BAD_REQUEST)
|
||||
|
|
|
@ -12,7 +12,7 @@ from homeassistant.const import (
|
|||
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
|
||||
CONF_BELOW, CONF_ABOVE)
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers import condition, config_validation as cv, template
|
||||
from homeassistant.helpers import condition, config_validation as cv
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'numeric_state',
|
||||
|
@ -32,7 +32,7 @@ def trigger(hass, config, action):
|
|||
above = config.get(CONF_ABOVE)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
value_template.hass = hass
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def state_automation_listener(entity, from_s, to_s):
|
||||
|
|
|
@ -4,13 +4,13 @@ Offer template automation rules.
|
|||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#template-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
|
||||
from homeassistant.helpers import condition, template
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
|
||||
from homeassistant.helpers import condition
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -25,21 +25,22 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
|
|||
|
||||
def trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
value_template = template.compile_template(
|
||||
hass, config.get(CONF_VALUE_TEMPLATE))
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
|
||||
# Local variable to keep track of if the action has already been triggered
|
||||
already_triggered = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def state_changed_listener(entity_id, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
nonlocal already_triggered
|
||||
template_result = condition.template(hass, value_template)
|
||||
template_result = condition.async_template(hass, value_template)
|
||||
|
||||
# Check to see if template returns true
|
||||
if template_result and not already_triggered:
|
||||
already_triggered = True
|
||||
action({
|
||||
hass.async_add_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'template',
|
||||
'entity_id': entity_id,
|
||||
|
@ -50,4 +51,5 @@ def trigger(hass, config, action):
|
|||
elif not template_result:
|
||||
already_triggered = False
|
||||
|
||||
return track_state_change(hass, MATCH_ALL, state_changed_listener)
|
||||
return track_state_change(hass, value_template.extract_entities(),
|
||||
state_changed_listener)
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.components.sensor.command_line import CommandSensorData
|
|||
from homeassistant.const import (
|
||||
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_COMMAND)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -45,10 +44,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
payload_on = config.get(CONF_PAYLOAD_ON)
|
||||
sensor_class = config.get(CONF_SENSOR_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
|
||||
value_template.hass = hass
|
||||
data = CommandSensorData(command)
|
||||
|
||||
add_devices([CommandBinarySensor(
|
||||
|
@ -94,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
|
|||
value = self.data.value
|
||||
|
||||
if self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, False)
|
||||
value = self._value_template.render_with_possible_json_value(
|
||||
value, False)
|
||||
if value == self._payload_on:
|
||||
self._state = True
|
||||
elif value == self._payload_off:
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
|||
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
|
||||
CONF_SENSOR_CLASS)
|
||||
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -38,10 +37,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
|||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT binary sensor."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
|
||||
value_template.hass = hass
|
||||
add_devices([MqttBinarySensor(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
|
@ -73,8 +70,8 @@ class MqttBinarySensor(BinarySensorDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._payload_on:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.components.sensor.rest import RestData
|
|||
from homeassistant.const import (
|
||||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -44,10 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
verify_ssl = config.get(CONF_VERIFY_SSL)
|
||||
sensor_class = config.get(CONF_SENSOR_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
|
||||
value_template.hass = hass
|
||||
rest = RestData(method, resource, payload, verify_ssl)
|
||||
rest.update()
|
||||
|
||||
|
@ -91,8 +88,8 @@ class RestBinarySensor(BinarySensorDevice):
|
|||
return False
|
||||
|
||||
if self._value_template is not None:
|
||||
response = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, self.rest.data, False)
|
||||
response = self._value_template.render_with_possible_json_value(
|
||||
self.rest.data, False)
|
||||
|
||||
try:
|
||||
return bool(int(response))
|
||||
|
|
|
@ -12,10 +12,9 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
|
||||
SENSOR_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, MATCH_ALL, CONF_VALUE_TEMPLATE,
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_SENSORS)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -25,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
|
||||
})
|
||||
|
||||
|
@ -40,10 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
value_template = device_config[CONF_VALUE_TEMPLATE]
|
||||
entity_ids = device_config[ATTR_ENTITY_ID]
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
value_template.extract_entities())
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
sensor_class = device_config.get(CONF_SENSOR_CLASS)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
sensors.append(
|
||||
BinarySensorTemplate(
|
||||
hass,
|
||||
|
@ -73,7 +76,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._sensor_class = sensor_class
|
||||
self._template = template.compile_template(hass, value_template)
|
||||
self._template = value_template
|
||||
self._state = None
|
||||
|
||||
self.update()
|
||||
|
@ -107,8 +110,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
try:
|
||||
self._state = template.render(
|
||||
self.hass, self._template).lower() == 'true'
|
||||
self._state = self._template.render().lower() == 'true'
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
A sensor that monitors trands in other components.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.template/
|
||||
https://home-assistant.io/components/sensor.trend/
|
||||
"""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
|
||||
class SensorTrend(BinarySensorDevice):
|
||||
"""Representation of a Template Sensor."""
|
||||
"""Representation of a trend Sensor."""
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
def __init__(self, hass, device_id, friendly_name,
|
||||
|
@ -90,14 +90,14 @@ class SensorTrend(BinarySensorDevice):
|
|||
|
||||
self.update()
|
||||
|
||||
def template_sensor_state_listener(entity, old_state, new_state):
|
||||
def trend_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
self.from_state = old_state
|
||||
self.to_state = new_state
|
||||
self.update_ha_state(True)
|
||||
|
||||
track_state_change(hass, target_entity,
|
||||
template_sensor_state_listener)
|
||||
trend_sensor_state_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
|
||||
from homeassistant.helpers import config_validation as cv, template
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
|
|||
DEFAULT_NAME = 'Generic Camera'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_STILL_IMAGE_URL): vol.Any(cv.url, cv.template),
|
||||
vol.Required(CONF_STILL_IMAGE_URL): cv.template,
|
||||
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
|
||||
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
|
||||
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
|
||||
|
@ -50,8 +50,8 @@ class GenericCamera(Camera):
|
|||
super().__init__()
|
||||
self.hass = hass
|
||||
self._name = device_info.get(CONF_NAME)
|
||||
self._still_image_url = template.compile_template(
|
||||
hass, device_info[CONF_STILL_IMAGE_URL])
|
||||
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
|
||||
self._still_image_url.hass = hass
|
||||
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
|
||||
|
||||
username = device_info.get(CONF_USERNAME)
|
||||
|
@ -71,7 +71,7 @@ class GenericCamera(Camera):
|
|||
def camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
try:
|
||||
url = template.render(self.hass, self._still_image_url)
|
||||
url = self._still_image_url.render()
|
||||
except TemplateError as err:
|
||||
_LOGGER.error('Error parsing template %s: %s',
|
||||
self._still_image_url, err)
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.const import (
|
|||
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
|
||||
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -39,9 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
for device_name, device_config in devices.items():
|
||||
value_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
value_template.hass = hass
|
||||
|
||||
covers.append(
|
||||
CommandCover(
|
||||
|
@ -141,8 +138,8 @@ class CommandCover(CoverDevice):
|
|||
if self._command_state:
|
||||
payload = str(self._query_state())
|
||||
if self._value_template:
|
||||
payload = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, payload)
|
||||
payload = self._value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
self._state = int(payload)
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
|||
STATE_CLOSED)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -49,10 +48,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
|||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT Cover."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = template.compile_template(hass, value_template)
|
||||
|
||||
value_template.hass = hass
|
||||
add_devices([MqttCover(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
|
@ -96,8 +93,8 @@ class MqttCover(CoverDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._state_open:
|
||||
self._state = False
|
||||
_LOGGER.warning("state=%s", int(self._state))
|
||||
|
|
|
@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation
|
|||
https://home-assistant.io/components/fan.mqtt/
|
||||
"""
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -16,7 +15,6 @@ from homeassistant.const import (
|
|||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import render_with_possible_json_value
|
||||
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
|
||||
SPEED_HIGH, FanEntity,
|
||||
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
|
||||
|
@ -139,9 +137,12 @@ class MqttFan(FanEntity):
|
|||
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
|
||||
is not None and SUPPORT_SET_SPEED)
|
||||
|
||||
templates = {key: ((lambda value: value) if tpl is None else
|
||||
partial(render_with_possible_json_value, hass, tpl))
|
||||
for key, tpl in templates.items()}
|
||||
for key, tpl in list(templates.items()):
|
||||
if tpl is None:
|
||||
templates[key] = lambda value: value
|
||||
else:
|
||||
tpl.hass = hass
|
||||
templates[key] = tpl.render_with_possible_json_value
|
||||
|
||||
def state_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
|
|
|
@ -16,7 +16,6 @@ from homeassistant.const import (
|
|||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -45,6 +44,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
|||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Add MQTT Garage Door."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices_callback([MqttGarageDoor(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
|
@ -57,7 +59,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
config[CONF_SERVICE_OPEN],
|
||||
config[CONF_SERVICE_CLOSE],
|
||||
config[CONF_OPTIMISTIC],
|
||||
config.get(CONF_VALUE_TEMPLATE))])
|
||||
value_template)])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
|
@ -84,8 +86,8 @@ class MqttGarageDoor(GarageDoorDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._state_open:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
|
|
@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
|
|||
https://home-assistant.io/components/light.mqtt/
|
||||
"""
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -19,7 +18,6 @@ from homeassistant.const import (
|
|||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import render_with_possible_json_value
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -117,9 +115,12 @@ class MqttLight(Light):
|
|||
topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and
|
||||
SUPPORT_BRIGHTNESS)
|
||||
|
||||
templates = {key: ((lambda value: value) if tpl is None else
|
||||
partial(render_with_possible_json_value, hass, tpl))
|
||||
for key, tpl in templates.items()}
|
||||
for key, tpl in list(templates.items()):
|
||||
if tpl is None:
|
||||
templates[key] = lambda value: value
|
||||
else:
|
||||
tpl.hass = hass
|
||||
templates[key] = tpl.render_with_possible_json_value
|
||||
|
||||
def state_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.components.mqtt import (
|
|||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -42,6 +41,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
|||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT lock."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices([MqttLock(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
|
@ -52,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
config.get(CONF_PAYLOAD_LOCK),
|
||||
config.get(CONF_PAYLOAD_UNLOCK),
|
||||
config.get(CONF_OPTIMISTIC),
|
||||
config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template,
|
||||
)])
|
||||
|
||||
|
||||
|
@ -77,8 +79,8 @@ class MqttLock(LockDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._payload_lock:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
|
|
@ -20,7 +20,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
|||
STATE_NOT_HOME, STATE_OFF, STATE_ON,
|
||||
ATTR_HIDDEN)
|
||||
from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN
|
||||
from homeassistant.helpers import template
|
||||
|
||||
DOMAIN = "logbook"
|
||||
DEPENDENCIES = ['recorder', 'frontend']
|
||||
|
@ -51,7 +50,7 @@ ATTR_ENTITY_ID = 'entity_id'
|
|||
|
||||
LOG_MESSAGE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_NAME): cv.string,
|
||||
vol.Required(ATTR_MESSAGE): cv.string,
|
||||
vol.Required(ATTR_MESSAGE): cv.template,
|
||||
vol.Optional(ATTR_DOMAIN): cv.slug,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
|
||||
})
|
||||
|
@ -80,7 +79,8 @@ def setup(hass, config):
|
|||
domain = service.data.get(ATTR_DOMAIN)
|
||||
entity_id = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
message = template.render(hass, message)
|
||||
message.hass = hass
|
||||
message = message.render()
|
||||
log_entry(hass, name, message, domain, entity_id)
|
||||
|
||||
hass.wsgi.register_view(LogbookView(hass, config))
|
||||
|
|
|
@ -264,8 +264,8 @@ def setup(hass, config):
|
|||
qos = call.data[ATTR_QOS]
|
||||
retain = call.data[ATTR_RETAIN]
|
||||
try:
|
||||
payload = (payload if payload_template is None else
|
||||
template.render(hass, payload_template)) or ''
|
||||
if payload_template is not None:
|
||||
payload = template.Template(payload_template, hass).render()
|
||||
except template.jinja2.TemplateError as exc:
|
||||
_LOGGER.error(
|
||||
"Unable to publish to '%s': rendering payload template of "
|
||||
|
|
|
@ -12,7 +12,7 @@ import voluptuous as vol
|
|||
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers import config_per_platform, template
|
||||
from homeassistant.helpers import config_per_platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import CONF_NAME, CONF_PLATFORM
|
||||
from homeassistant.util import slugify
|
||||
|
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = vol.Schema({
|
|||
|
||||
NOTIFY_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_MESSAGE): cv.template,
|
||||
vol.Optional(ATTR_TITLE): cv.string,
|
||||
vol.Optional(ATTR_TITLE): cv.template,
|
||||
vol.Optional(ATTR_TARGET): cv.string,
|
||||
vol.Optional(ATTR_DATA): dict,
|
||||
})
|
||||
|
@ -96,14 +96,16 @@ def setup(hass, config):
|
|||
title = call.data.get(ATTR_TITLE)
|
||||
|
||||
if title:
|
||||
kwargs[ATTR_TITLE] = template.render(hass, title)
|
||||
title.hass = hass
|
||||
kwargs[ATTR_TITLE] = title.render()
|
||||
|
||||
if targets.get(call.service) is not None:
|
||||
kwargs[ATTR_TARGET] = targets[call.service]
|
||||
else:
|
||||
kwargs[ATTR_TARGET] = call.data.get(ATTR_TARGET)
|
||||
|
||||
kwargs[ATTR_MESSAGE] = template.render(hass, message)
|
||||
message.hass = hass
|
||||
kwargs[ATTR_MESSAGE] = message.render()
|
||||
kwargs[ATTR_DATA] = call.data.get(ATTR_DATA)
|
||||
|
||||
notify_service.send_message(**kwargs)
|
||||
|
|
|
@ -10,7 +10,7 @@ import logging
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template, config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
|
@ -63,16 +63,20 @@ def setup(hass, config):
|
|||
attr = {}
|
||||
if title is not None:
|
||||
try:
|
||||
title = template.render(hass, title)
|
||||
title.hass = hass
|
||||
title = title.render()
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error('Error rendering title %s: %s', title, ex)
|
||||
title = title.template
|
||||
|
||||
attr[ATTR_TITLE] = title
|
||||
|
||||
try:
|
||||
message = template.render(hass, message)
|
||||
message.hass = hass
|
||||
message = message.render()
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error('Error rendering message %s: %s', message, ex)
|
||||
message = message.template
|
||||
|
||||
hass.states.set(entity_id, message, attr)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import subprocess
|
|||
|
||||
from homeassistant.components.rollershutter import RollershutterDevice
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.template import Template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -20,6 +20,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
devices = []
|
||||
|
||||
for dev_name, properties in rollershutters.items():
|
||||
value_template = properties.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = Template(value_template, hass)
|
||||
|
||||
devices.append(
|
||||
CommandRollershutter(
|
||||
hass,
|
||||
|
@ -28,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
properties.get('downcmd', 'true'),
|
||||
properties.get('stopcmd', 'true'),
|
||||
properties.get('statecmd', False),
|
||||
properties.get(CONF_VALUE_TEMPLATE, '{{ value }}')))
|
||||
value_template))
|
||||
add_devices_callback(devices)
|
||||
|
||||
|
||||
|
@ -103,8 +108,8 @@ class CommandRollershutter(RollershutterDevice):
|
|||
if self._command_state:
|
||||
payload = str(self._query_state())
|
||||
if self._value_template:
|
||||
payload = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, payload)
|
||||
payload = self._value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
self._state = int(payload)
|
||||
|
||||
def move_up(self, **kwargs):
|
||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.components.rollershutter import RollershutterDevice
|
|||
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -39,6 +38,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
|||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Add MQTT Rollershutter."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices_callback([MqttRollershutter(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
|
@ -48,7 +50,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
config[CONF_PAYLOAD_UP],
|
||||
config[CONF_PAYLOAD_DOWN],
|
||||
config[CONF_PAYLOAD_STOP],
|
||||
config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template,
|
||||
)])
|
||||
|
||||
|
||||
|
@ -76,8 +78,8 @@ class MqttRollershutter(RollershutterDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload.isnumeric() and 0 <= int(payload) <= 100:
|
||||
self._state = int(payload)
|
||||
self.update_ha_state()
|
||||
|
|
|
@ -52,9 +52,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
if value_template is None:
|
||||
return lambda value: value
|
||||
|
||||
value_template = template.Template(value_template, hass)
|
||||
|
||||
def _render(value):
|
||||
try:
|
||||
return template.render(hass, value_template, {'value': value})
|
||||
return value_template.render({'value': value})
|
||||
except TemplateError:
|
||||
_LOGGER.exception('Error parsing value')
|
||||
return value
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -39,7 +38,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
command = config.get(CONF_COMMAND)
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
data = CommandSensorData(command)
|
||||
|
||||
add_devices([CommandSensor(hass, data, name, unit, value_template)])
|
||||
|
@ -80,8 +80,8 @@ class CommandSensor(Entity):
|
|||
value = self.data.value
|
||||
|
||||
if self._value_template is not None:
|
||||
self._state = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, 'N/A')
|
||||
self._state = self._value_template.render_with_possible_json_value(
|
||||
value, 'N/A')
|
||||
else:
|
||||
self._state = value
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
|||
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['dweepy==0.2.0']
|
||||
|
@ -45,16 +44,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
device = config.get(CONF_DEVICE)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
|
||||
value_template.hass = hass
|
||||
try:
|
||||
content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content'])
|
||||
except dweepy.DweepyError:
|
||||
_LOGGER.error("Device/thing '%s' could not be found", device)
|
||||
return False
|
||||
|
||||
if template.render_with_possible_json_value(hass,
|
||||
value_template,
|
||||
content) is '':
|
||||
if value_template.render_with_possible_json_value(content) == '':
|
||||
_LOGGER.error("'%s' was not found", value_template)
|
||||
return False
|
||||
|
||||
|
@ -94,8 +91,8 @@ class DweetSensor(Entity):
|
|||
return STATE_UNKNOWN
|
||||
else:
|
||||
values = json.dumps(self.dweet.data[0]['content'])
|
||||
value = template.render_with_possible_json_value(
|
||||
self.hass, self._value_template, values)
|
||||
value = self._value_template.render_with_possible_json_value(
|
||||
values)
|
||||
return value
|
||||
|
||||
def update(self):
|
||||
|
|
|
@ -70,6 +70,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
sensor_names = config.get(CONF_SENSOR_NAMES)
|
||||
interval = config.get(CONF_SCAN_INTERVAL)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
data = EmonCmsData(hass, url, apikey, interval)
|
||||
|
||||
data.update()
|
||||
|
@ -123,9 +126,8 @@ class EmonCmsSensor(Entity):
|
|||
self._elem = elem
|
||||
|
||||
if self._value_template is not None:
|
||||
self._state = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, elem["value"],
|
||||
STATE_UNKNOWN)
|
||||
self._state = self._value_template.render_with_possible_json_value(
|
||||
elem["value"], STATE_UNKNOWN)
|
||||
else:
|
||||
self._state = round(float(elem["value"]), DECIMALS)
|
||||
|
||||
|
@ -177,9 +179,8 @@ class EmonCmsSensor(Entity):
|
|||
self._elem = elem
|
||||
|
||||
if self._value_template is not None:
|
||||
self._state = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, elem["value"],
|
||||
STATE_UNKNOWN)
|
||||
self._state = self._value_template.render_with_possible_json_value(
|
||||
elem["value"], STATE_UNKNOWN)
|
||||
else:
|
||||
self._state = round(float(elem["value"]), DECIMALS)
|
||||
|
||||
|
|
|
@ -12,16 +12,14 @@ from collections import deque
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD)
|
||||
CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_VALUE_TEMPLATE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import template
|
||||
import voluptuous as vol
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SERVER = "server"
|
||||
CONF_SENDERS = "senders"
|
||||
CONF_VALUE_TEMPLATE = "value_template"
|
||||
|
||||
ATTR_FROM = "from"
|
||||
ATTR_BODY = "body"
|
||||
|
@ -48,12 +46,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
config.get(CONF_SERVER),
|
||||
config.get(CONF_PORT))
|
||||
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
sensor = EmailContentSensor(
|
||||
hass,
|
||||
reader,
|
||||
config.get(CONF_NAME, None) or config.get(CONF_USERNAME),
|
||||
config.get(CONF_SENDERS),
|
||||
config.get(CONF_VALUE_TEMPLATE))
|
||||
value_template)
|
||||
|
||||
if sensor.connected:
|
||||
add_devices([sensor])
|
||||
|
@ -172,7 +173,7 @@ class EmailContentSensor(Entity):
|
|||
ATTR_DATE: email_message['Date'],
|
||||
ATTR_BODY: EmailContentSensor.get_msg_text(email_message)
|
||||
}
|
||||
return template.render(self.hass, self._value_template, variables)
|
||||
return self._value_template.render(variables)
|
||||
|
||||
def sender_allowed(self, email_message):
|
||||
"""Check if the sender is in the allowed senders list."""
|
||||
|
|
|
@ -11,7 +11,6 @@ import voluptuous as vol
|
|||
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.entity import Entity
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -30,13 +29,16 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
|||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup MQTT Sensor."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices([MqttSensor(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_UNIT_OF_MEASUREMENT),
|
||||
config.get(CONF_VALUE_TEMPLATE),
|
||||
value_template,
|
||||
)])
|
||||
|
||||
|
||||
|
@ -57,8 +59,8 @@ class MqttSensor(Entity):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
self._state = payload
|
||||
self.update_ha_state()
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.const import (
|
|||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, CONF_VERIFY_SSL)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -44,7 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
verify_ssl = config.get(CONF_VERIFY_SSL)
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
rest = RestData(method, resource, payload, verify_ssl)
|
||||
rest.update()
|
||||
|
||||
|
@ -92,8 +92,8 @@ class RestSensor(Entity):
|
|||
if value is None:
|
||||
value = STATE_UNKNOWN
|
||||
elif self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, STATE_UNKNOWN)
|
||||
value = self._value_template.render_with_possible_json_value(
|
||||
value, STATE_UNKNOWN)
|
||||
|
||||
self._state = value
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ import socket
|
|||
import select
|
||||
|
||||
from homeassistant.const import CONF_NAME, CONF_HOST
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.template import Template
|
||||
|
||||
CONF_PORT = "port"
|
||||
CONF_TIMEOUT = "timeout"
|
||||
|
@ -41,6 +41,11 @@ class Sensor(Entity):
|
|||
|
||||
def __init__(self, hass, config):
|
||||
"""Set all the config values if they exist and get initial state."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template = Template(value_template, hass)
|
||||
|
||||
self._hass = hass
|
||||
self._config = {
|
||||
CONF_NAME: config.get(CONF_NAME),
|
||||
|
@ -49,7 +54,7 @@ class Sensor(Entity):
|
|||
CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
|
||||
CONF_PAYLOAD: config[CONF_PAYLOAD],
|
||||
CONF_UNIT: config.get(CONF_UNIT),
|
||||
CONF_VALUE_TEMPLATE: config.get(CONF_VALUE_TEMPLATE),
|
||||
CONF_VALUE_TEMPLATE: value_template,
|
||||
CONF_VALUE_ON: config.get(CONF_VALUE_ON),
|
||||
CONF_BUFFER_SIZE: config.get(
|
||||
CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE),
|
||||
|
@ -122,9 +127,7 @@ class Sensor(Entity):
|
|||
|
||||
if self._config[CONF_VALUE_TEMPLATE] is not None:
|
||||
try:
|
||||
self._state = template.render(
|
||||
self._hass,
|
||||
self._config[CONF_VALUE_TEMPLATE],
|
||||
self._state = self._config[CONF_VALUE_TEMPLATE].render(
|
||||
value=value)
|
||||
return
|
||||
except TemplateError as err:
|
||||
|
|
|
@ -11,9 +11,8 @@ import voluptuous as vol
|
|||
from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
|
||||
ATTR_ENTITY_ID, MATCH_ALL, CONF_SENSORS)
|
||||
ATTR_ENTITY_ID, CONF_SENSORS)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.entity import Entity, generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -24,7 +23,7 @@ SENSOR_SCHEMA = vol.Schema({
|
|||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
@ -39,10 +38,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
state_template = device_config[CONF_VALUE_TEMPLATE]
|
||||
entity_ids = device_config[ATTR_ENTITY_ID]
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
state_template.extract_entities())
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
state_template.hass = hass
|
||||
|
||||
sensors.append(
|
||||
SensorTemplate(
|
||||
hass,
|
||||
|
@ -71,7 +73,7 @@ class SensorTemplate(Entity):
|
|||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._template = template.compile_template(hass, state_template)
|
||||
self._template = state_template
|
||||
self._state = None
|
||||
|
||||
self.update()
|
||||
|
@ -105,7 +107,7 @@ class SensorTemplate(Entity):
|
|||
def update(self):
|
||||
"""Get the latest data and update the states."""
|
||||
try:
|
||||
self._state = template.render(self.hass, self._template)
|
||||
self._state = self._template.render()
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
|
|
|
@ -29,12 +29,41 @@ def setup(hass, config):
|
|||
"""Setup the shell_command component."""
|
||||
conf = config.get(DOMAIN, {})
|
||||
|
||||
cache = {}
|
||||
|
||||
def service_handler(call):
|
||||
"""Execute a shell command service."""
|
||||
cmd = conf[call.service]
|
||||
cmd, shell = _parse_command(hass, cmd, call.data)
|
||||
if cmd is None:
|
||||
return
|
||||
|
||||
if cmd in cache:
|
||||
prog, args, args_compiled = cache[cmd]
|
||||
elif ' ' not in cmd:
|
||||
prog = cmd
|
||||
args = None
|
||||
args_compiled = None
|
||||
cache[cmd] = prog, args, args_compiled
|
||||
else:
|
||||
prog, args = cmd.split(' ', 1)
|
||||
args_compiled = template.Template(args, hass)
|
||||
cache[cmd] = prog, args, args_compiled
|
||||
|
||||
if args_compiled:
|
||||
try:
|
||||
rendered_args = args_compiled.render(call.data)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.exception('Error rendering command template: %s', ex)
|
||||
return
|
||||
else:
|
||||
rendered_args = None
|
||||
|
||||
if rendered_args == args:
|
||||
# no template used. default behavior
|
||||
shell = True
|
||||
else:
|
||||
# template used. Break into list and use shell=False for security
|
||||
cmd = [prog] + shlex.split(rendered_args)
|
||||
shell = False
|
||||
|
||||
try:
|
||||
subprocess.call(cmd, shell=shell,
|
||||
stdout=subprocess.DEVNULL,
|
||||
|
@ -45,23 +74,3 @@ def setup(hass, config):
|
|||
for name in conf.keys():
|
||||
hass.services.register(DOMAIN, name, service_handler)
|
||||
return True
|
||||
|
||||
|
||||
def _parse_command(hass, cmd, variables):
|
||||
"""Parse command and fill in any template arguments if necessary."""
|
||||
cmds = cmd.split()
|
||||
prog = cmds[0]
|
||||
args = ' '.join(cmds[1:])
|
||||
try:
|
||||
rendered_args = template.render(hass, args, variables=variables)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.exception('Error rendering command template: %s', ex)
|
||||
return None, None
|
||||
if rendered_args == args:
|
||||
# no template used. default behavior
|
||||
shell = True
|
||||
else:
|
||||
# template used. Must break into list and use shell=False for security
|
||||
cmd = [prog] + shlex.split(rendered_args)
|
||||
shell = False
|
||||
return cmd, shell
|
||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
|
|||
from homeassistant.const import (
|
||||
CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF,
|
||||
CONF_COMMAND_ON, CONF_COMMAND_STATE)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -38,6 +37,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
switches = []
|
||||
|
||||
for device_name, device_config in devices.items():
|
||||
value_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
switches.append(
|
||||
CommandSwitch(
|
||||
hass,
|
||||
|
@ -45,7 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
device_config.get(CONF_COMMAND_ON),
|
||||
device_config.get(CONF_COMMAND_OFF),
|
||||
device_config.get(CONF_COMMAND_STATE),
|
||||
device_config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -135,8 +139,8 @@ class CommandSwitch(SwitchDevice):
|
|||
if self._command_state:
|
||||
payload = str(self._query_state())
|
||||
if self._value_template:
|
||||
payload = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, payload)
|
||||
payload = self._value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
self._state = (payload.lower() == "true")
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.components.switch import SwitchDevice
|
|||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
|
||||
CONF_PAYLOAD_ON)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -38,6 +37,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
|||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT switch."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices([MqttSwitch(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
|
@ -48,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
config.get(CONF_PAYLOAD_ON),
|
||||
config.get(CONF_PAYLOAD_OFF),
|
||||
config.get(CONF_OPTIMISTIC),
|
||||
config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template,
|
||||
)])
|
||||
|
||||
|
||||
|
@ -73,8 +75,8 @@ class MqttSwitch(SwitchDevice):
|
|||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._payload_on:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
|
|
@ -12,9 +12,8 @@ from homeassistant.components.switch import (
|
|||
ENTITY_ID_FORMAT, SwitchDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
|
||||
ATTR_ENTITY_ID, MATCH_ALL, CONF_SWITCHES)
|
||||
ATTR_ENTITY_ID, CONF_SWITCHES)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.script import Script
|
||||
|
@ -31,7 +30,7 @@ SWITCH_SCHEMA = vol.Schema({
|
|||
vol.Required(ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
@ -49,7 +48,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
state_template = device_config[CONF_VALUE_TEMPLATE]
|
||||
on_action = device_config[ON_ACTION]
|
||||
off_action = device_config[OFF_ACTION]
|
||||
entity_ids = device_config[ATTR_ENTITY_ID]
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
state_template.extract_entities())
|
||||
|
||||
switches.append(
|
||||
SwitchTemplate(
|
||||
|
@ -79,7 +79,8 @@ class SwitchTemplate(SwitchDevice):
|
|||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._template = template.compile_template(hass, state_template)
|
||||
self._template = state_template
|
||||
state_template.hass = hass
|
||||
self._on_script = Script(hass, on_action)
|
||||
self._off_script = Script(hass, off_action)
|
||||
self._state = False
|
||||
|
@ -123,7 +124,7 @@ class SwitchTemplate(SwitchDevice):
|
|||
def update(self):
|
||||
"""Update the state from the template."""
|
||||
try:
|
||||
state = template.render(self.hass, self._template).lower()
|
||||
state = self._template.render().lower()
|
||||
|
||||
if state in _VALID_STATES:
|
||||
self._state = state in ('true', STATE_ON)
|
||||
|
|
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
|||
CONF_BELOW, CONF_ABOVE)
|
||||
from homeassistant.exceptions import TemplateError, HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import render, compile_template
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
FROM_CONFIG_FORMAT = '{}_from_config'
|
||||
|
||||
|
@ -41,7 +41,7 @@ def and_from_config(config: ConfigType, config_validation: bool=True):
|
|||
"""Create multi condition matcher using 'AND'."""
|
||||
if config_validation:
|
||||
config = cv.AND_CONDITION_SCHEMA(config)
|
||||
checks = [from_config(entry) for entry in config['conditions']]
|
||||
checks = [from_config(entry, False) for entry in config['conditions']]
|
||||
|
||||
def if_and_condition(hass: HomeAssistant,
|
||||
variables=None) -> bool:
|
||||
|
@ -63,7 +63,7 @@ def or_from_config(config: ConfigType, config_validation: bool=True):
|
|||
"""Create multi condition matcher using 'OR'."""
|
||||
if config_validation:
|
||||
config = cv.OR_CONDITION_SCHEMA(config)
|
||||
checks = [from_config(entry) for entry in config['conditions']]
|
||||
checks = [from_config(entry, False) for entry in config['conditions']]
|
||||
|
||||
def if_or_condition(hass: HomeAssistant,
|
||||
variables=None) -> bool:
|
||||
|
@ -96,7 +96,7 @@ def numeric_state(hass: HomeAssistant, entity, below=None, above=None,
|
|||
variables = dict(variables or {})
|
||||
variables['state'] = entity
|
||||
try:
|
||||
value = render(hass, value_template, variables)
|
||||
value = value_template.render(variables)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error("Template error: %s", ex)
|
||||
return False
|
||||
|
@ -125,18 +125,12 @@ def numeric_state_from_config(config, config_validation=True):
|
|||
above = config.get(CONF_ABOVE)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
cache = {}
|
||||
|
||||
def if_numeric_state(hass, variables=None):
|
||||
"""Test numeric state condition."""
|
||||
if value_template is None:
|
||||
tmpl = None
|
||||
elif hass in cache:
|
||||
tmpl = cache[hass]
|
||||
else:
|
||||
cache[hass] = tmpl = compile_template(hass, value_template)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
return numeric_state(hass, entity_id, below, above, tmpl,
|
||||
return numeric_state(hass, entity_id, below, above, value_template,
|
||||
variables)
|
||||
|
||||
return if_numeric_state
|
||||
|
@ -215,9 +209,16 @@ def sun_from_config(config, config_validation=True):
|
|||
|
||||
|
||||
def template(hass, value_template, variables=None):
|
||||
"""Test if template condition matches."""
|
||||
return run_callback_threadsafe(
|
||||
hass.loop, async_template, hass, value_template, variables,
|
||||
).result()
|
||||
|
||||
|
||||
def async_template(hass, value_template, variables=None):
|
||||
"""Test if template condition matches."""
|
||||
try:
|
||||
value = render(hass, value_template, variables)
|
||||
value = value_template.async_render(variables)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error('Error duriong template condition: %s', ex)
|
||||
return False
|
||||
|
@ -231,16 +232,11 @@ def template_from_config(config, config_validation=True):
|
|||
config = cv.TEMPLATE_CONDITION_SCHEMA(config)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
cache = {}
|
||||
|
||||
def template_if(hass, variables=None):
|
||||
"""Validate template based if-condition."""
|
||||
if hass in cache:
|
||||
tmpl = cache[hass]
|
||||
else:
|
||||
cache[hass] = tmpl = compile_template(hass, value_template)
|
||||
value_template.hass = hass
|
||||
|
||||
return template(hass, tmpl, variables)
|
||||
return template(hass, value_template, variables)
|
||||
|
||||
return template_if
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ from urllib.parse import urlparse
|
|||
|
||||
from typing import Any, Union, TypeVar, Callable, Sequence, Dict
|
||||
|
||||
import jinja2
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.loader import get_platform
|
||||
|
@ -16,8 +15,10 @@ from homeassistant.const import (
|
|||
CONF_CONDITION, CONF_BELOW, CONF_ABOVE, SUN_EVENT_SUNSET,
|
||||
SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC)
|
||||
from homeassistant.core import valid_entity_id
|
||||
from homeassistant.exceptions import TemplateError
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.helpers import template as template_helper
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
|
@ -103,6 +104,11 @@ def entity_ids(value: Union[str, Sequence]) -> Sequence[str]:
|
|||
return [entity_id(ent_id) for ent_id in value]
|
||||
|
||||
|
||||
def enum(enumClass):
|
||||
"""Create validator for specified enum."""
|
||||
return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__)
|
||||
|
||||
|
||||
def icon(value):
|
||||
"""Validate icon."""
|
||||
value = str(value)
|
||||
|
@ -234,14 +240,15 @@ def template(value):
|
|||
"""Validate a jinja2 template."""
|
||||
if value is None:
|
||||
raise vol.Invalid('template value is None')
|
||||
if isinstance(value, (list, dict)):
|
||||
elif isinstance(value, (list, dict, template_helper.Template)):
|
||||
raise vol.Invalid('template value should be a string')
|
||||
|
||||
value = str(value)
|
||||
value = template_helper.Template(str(value))
|
||||
|
||||
try:
|
||||
jinja2.Environment().parse(value)
|
||||
value.ensure_valid()
|
||||
return value
|
||||
except jinja2.exceptions.TemplateSyntaxError as ex:
|
||||
except TemplateError as ex:
|
||||
raise vol.Invalid('invalid template ({})'.format(ex))
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ CONF_DELAY = "delay"
|
|||
def call_from_config(hass: HomeAssistant, config: ConfigType,
|
||||
variables: Optional[Sequence]=None) -> None:
|
||||
"""Call a script based on a config entry."""
|
||||
Script(hass, config).run(variables)
|
||||
Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables)
|
||||
|
||||
|
||||
class Script():
|
||||
|
@ -39,7 +39,8 @@ class Script():
|
|||
change_listener=None) -> None:
|
||||
"""Initialize the script."""
|
||||
self.hass = hass
|
||||
self.sequence = cv.SCRIPT_SCHEMA(sequence)
|
||||
self.sequence = sequence
|
||||
template.attach(hass, self.sequence)
|
||||
self.name = name
|
||||
self._change_listener = change_listener
|
||||
self._cur = -1
|
||||
|
@ -48,6 +49,7 @@ class Script():
|
|||
in self.sequence)
|
||||
self._lock = threading.Lock()
|
||||
self._unsub_delay_listener = None
|
||||
self._template_cache = {}
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
|
@ -77,11 +79,11 @@ class Script():
|
|||
|
||||
delay = action[CONF_DELAY]
|
||||
|
||||
if isinstance(delay, str):
|
||||
if isinstance(delay, template.Template):
|
||||
delay = vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta)(
|
||||
template.render(self.hass, delay))
|
||||
delay.render())
|
||||
|
||||
self._unsub_delay_listener = track_point_in_utc_time(
|
||||
self.hass, script_delay,
|
||||
|
@ -133,7 +135,7 @@ class Script():
|
|||
def _check_condition(self, action, variables):
|
||||
"""Test if condition is matching."""
|
||||
self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION])
|
||||
check = condition.from_config(action)(self.hass, variables)
|
||||
check = condition.from_config(action, False)(self.hass, variables)
|
||||
self._log("Test condition {}: {}".format(self.last_action, check))
|
||||
return check
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import voluptuous as vol
|
|||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant # NOQA
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -49,8 +48,8 @@ def call_from_config(hass, config, blocking=False, variables=None,
|
|||
domain_service = config[CONF_SERVICE]
|
||||
else:
|
||||
try:
|
||||
domain_service = template.render(
|
||||
hass, config[CONF_SERVICE_TEMPLATE], variables)
|
||||
config[CONF_SERVICE_TEMPLATE].hass = hass
|
||||
domain_service = config[CONF_SERVICE_TEMPLATE].render(variables)
|
||||
domain_service = cv.service(domain_service)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error('Error rendering service name template: %s', ex)
|
||||
|
@ -73,7 +72,8 @@ def call_from_config(hass, config, blocking=False, variables=None,
|
|||
for key, element in value.items():
|
||||
value[key] = _data_template_creator(element)
|
||||
return value
|
||||
return template.render(hass, value, variables)
|
||||
value.hass = hass
|
||||
return value.render(variables)
|
||||
|
||||
if CONF_SERVICE_DATA_TEMPLATE in config:
|
||||
for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items():
|
||||
|
|
|
@ -2,73 +2,162 @@
|
|||
# pylint: disable=too-few-public-methods
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
import jinja2
|
||||
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
||||
|
||||
from homeassistant.const import STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE
|
||||
from homeassistant.const import (
|
||||
STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL)
|
||||
from homeassistant.core import State
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import location as loc_helper
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.util import convert, dt as dt_util, location as loc_util
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_SENTINEL = object()
|
||||
DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
|
||||
def compile_template(hass, template):
|
||||
"""Compile a template."""
|
||||
location_methods = LocationMethods(hass)
|
||||
|
||||
return ENV.from_string(template, {
|
||||
'closest': location_methods.closest,
|
||||
'distance': location_methods.distance,
|
||||
'float': forgiving_float,
|
||||
'is_state': hass.states.is_state,
|
||||
'is_state_attr': hass.states.is_state_attr,
|
||||
'now': dt_util.now,
|
||||
'states': AllStates(hass),
|
||||
'utcnow': dt_util.utcnow,
|
||||
'as_timestamp': dt_util.as_timestamp,
|
||||
'relative_time': dt_util.get_age
|
||||
})
|
||||
_RE_NONE_ENTITIES = re.compile(r"distance\(|closest\(", re.I | re.M)
|
||||
_RE_GET_ENTITIES = re.compile(
|
||||
r"(?:(?:states\.|(?:is_state|is_state_attr|states)\(.)([\w]+\.[\w]+))",
|
||||
re.I | re.M
|
||||
)
|
||||
|
||||
|
||||
def render_with_possible_json_value(hass, template, value,
|
||||
error_value=_SENTINEL):
|
||||
"""Render template with value exposed.
|
||||
|
||||
If valid JSON will expose value_json too.
|
||||
"""
|
||||
variables = {
|
||||
'value': value
|
||||
}
|
||||
try:
|
||||
variables['value_json'] = json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return render(hass, template, variables)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error('Error parsing value: %s', ex)
|
||||
return value if error_value is _SENTINEL else error_value
|
||||
def attach(hass, obj):
|
||||
"""Recursively attach hass to all template instances in list and dict."""
|
||||
if isinstance(obj, list):
|
||||
for child in obj:
|
||||
attach(hass, child)
|
||||
elif isinstance(obj, dict):
|
||||
for child in obj.values():
|
||||
attach(hass, child)
|
||||
elif isinstance(obj, Template):
|
||||
obj.hass = hass
|
||||
|
||||
|
||||
def render(hass, template, variables=None, **kwargs):
|
||||
"""Render given template."""
|
||||
if variables is not None:
|
||||
kwargs.update(variables)
|
||||
def extract_entities(template):
|
||||
"""Extract all entities for state_changed listener from template string."""
|
||||
if template is None or _RE_NONE_ENTITIES.search(template):
|
||||
return MATCH_ALL
|
||||
|
||||
try:
|
||||
if not isinstance(template, jinja2.Template):
|
||||
template = compile_template(hass, template)
|
||||
extraction = _RE_GET_ENTITIES.findall(template)
|
||||
if len(extraction) > 0:
|
||||
return list(set(extraction))
|
||||
return MATCH_ALL
|
||||
|
||||
return template.render(kwargs).strip()
|
||||
except jinja2.TemplateError as err:
|
||||
raise TemplateError(err)
|
||||
|
||||
class Template(object):
|
||||
"""Class to hold a template and manage caching and rendering."""
|
||||
|
||||
def __init__(self, template, hass=None):
|
||||
"""Instantiate a Template."""
|
||||
if not isinstance(template, str):
|
||||
raise TypeError('Expected template to be a string')
|
||||
|
||||
self.template = template
|
||||
self._compiled_code = None
|
||||
self._compiled = None
|
||||
self.hass = hass
|
||||
|
||||
def ensure_valid(self):
|
||||
"""Return if template is valid."""
|
||||
if self._compiled_code is not None:
|
||||
return
|
||||
|
||||
try:
|
||||
self._compiled_code = ENV.compile(self.template)
|
||||
except jinja2.exceptions.TemplateSyntaxError as err:
|
||||
raise TemplateError(err)
|
||||
|
||||
def extract_entities(self):
|
||||
"""Extract all entities for state_changed listener."""
|
||||
return extract_entities(self.template)
|
||||
|
||||
def render(self, variables=None, **kwargs):
|
||||
"""Render given template."""
|
||||
if variables is not None:
|
||||
kwargs.update(variables)
|
||||
|
||||
return run_callback_threadsafe(
|
||||
self.hass.loop, self.async_render, kwargs).result()
|
||||
|
||||
def async_render(self, variables=None, **kwargs):
|
||||
"""Render given template.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
self._ensure_compiled()
|
||||
|
||||
if variables is not None:
|
||||
kwargs.update(variables)
|
||||
|
||||
try:
|
||||
return self._compiled.render(kwargs).strip()
|
||||
except jinja2.TemplateError as err:
|
||||
raise TemplateError(err)
|
||||
|
||||
def render_with_possible_json_value(self, value, error_value=_SENTINEL):
|
||||
"""Render template with value exposed.
|
||||
|
||||
If valid JSON will expose value_json too.
|
||||
"""
|
||||
return run_callback_threadsafe(
|
||||
self.hass.loop, self.async_render_with_possible_json_value, value,
|
||||
error_value).result()
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def async_render_with_possible_json_value(self, value,
|
||||
error_value=_SENTINEL):
|
||||
"""Render template with value exposed.
|
||||
|
||||
If valid JSON will expose value_json too.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
self._ensure_compiled()
|
||||
|
||||
variables = {
|
||||
'value': value
|
||||
}
|
||||
try:
|
||||
variables['value_json'] = json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return self._compiled.render(variables).strip()
|
||||
except jinja2.TemplateError as ex:
|
||||
_LOGGER.error('Error parsing value: %s (value: %s, template: %s)',
|
||||
ex, value, self.template)
|
||||
return value if error_value is _SENTINEL else error_value
|
||||
|
||||
def _ensure_compiled(self):
|
||||
"""Bind a template to a specific hass instance."""
|
||||
if self._compiled is not None:
|
||||
return
|
||||
|
||||
self.ensure_valid()
|
||||
|
||||
assert self.hass is not None, 'hass variable not set on template'
|
||||
|
||||
location_methods = LocationMethods(self.hass)
|
||||
|
||||
global_vars = ENV.make_globals({
|
||||
'closest': location_methods.closest,
|
||||
'distance': location_methods.distance,
|
||||
'is_state': self.hass.states.async_is_state,
|
||||
'is_state_attr': self.hass.states.async_is_state_attr,
|
||||
'states': AllStates(self.hass),
|
||||
})
|
||||
|
||||
self._compiled = jinja2.Template.from_code(
|
||||
ENV, self._compiled_code, global_vars, None)
|
||||
|
||||
return self._compiled
|
||||
|
||||
|
||||
class AllStates(object):
|
||||
|
@ -84,7 +173,7 @@ class AllStates(object):
|
|||
|
||||
def __iter__(self):
|
||||
"""Return all states."""
|
||||
return iter(sorted(self._hass.states.all(),
|
||||
return iter(sorted(self._hass.states.async_all(),
|
||||
key=lambda state: state.entity_id))
|
||||
|
||||
def __call__(self, entity_id):
|
||||
|
@ -108,7 +197,7 @@ class DomainStates(object):
|
|||
def __iter__(self):
|
||||
"""Return the iteration over all the states."""
|
||||
return iter(sorted(
|
||||
(state for state in self._hass.states.all()
|
||||
(state for state in self._hass.states.async_all()
|
||||
if state.domain == self._domain),
|
||||
key=lambda state: state.entity_id))
|
||||
|
||||
|
@ -313,3 +402,8 @@ ENV.filters['multiply'] = multiply
|
|||
ENV.filters['timestamp_custom'] = timestamp_custom
|
||||
ENV.filters['timestamp_local'] = timestamp_local
|
||||
ENV.filters['timestamp_utc'] = timestamp_utc
|
||||
ENV.globals['float'] = forgiving_float
|
||||
ENV.globals['now'] = dt_util.now
|
||||
ENV.globals['utcnow'] = dt_util.utcnow
|
||||
ENV.globals['as_timestamp'] = dt_util.as_timestamp
|
||||
ENV.globals['relative_time'] = dt_util.get_age
|
||||
|
|
|
@ -4,6 +4,7 @@ import unittest
|
|||
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||
from homeassistant.components.binary_sensor import command_line
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.helpers import template
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
@ -56,7 +57,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase):
|
|||
|
||||
entity = command_line.CommandBinarySensor(
|
||||
self.hass, data, 'test', None, '1.0', '0',
|
||||
'{{ value | multiply(0.1) }}')
|
||||
template.Template('{{ value | multiply(0.1) }}', self.hass))
|
||||
|
||||
self.assertEqual(STATE_ON, entity.state)
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ from unittest import mock
|
|||
|
||||
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.components.binary_sensor import template
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template as template_hlpr
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
@ -13,31 +15,39 @@ from tests.common import get_test_home_assistant
|
|||
class TestBinarySensorTemplate(unittest.TestCase):
|
||||
"""Test for Binary sensor template platform."""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
|
||||
def teardown_method(self, method):
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
@mock.patch.object(template, 'BinarySensorTemplate')
|
||||
def test_setup(self, mock_template):
|
||||
""""Test the setup."""
|
||||
config = {
|
||||
tpl = template_hlpr.Template('{{ foo }}', self.hass)
|
||||
config = PLATFORM_SCHEMA({
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'test': {
|
||||
'friendly_name': 'virtual thingy',
|
||||
'value_template': '{{ foo }}',
|
||||
'value_template': tpl,
|
||||
'sensor_class': 'motion',
|
||||
'entity_id': 'test'
|
||||
},
|
||||
}
|
||||
}
|
||||
hass = mock.MagicMock()
|
||||
})
|
||||
add_devices = mock.MagicMock()
|
||||
result = template.setup_platform(hass, config, add_devices)
|
||||
result = template.setup_platform(self.hass, config, add_devices)
|
||||
self.assertTrue(result)
|
||||
mock_template.assert_called_once_with(hass, 'test', 'virtual thingy',
|
||||
'motion', '{{ foo }}', 'test')
|
||||
mock_template.assert_called_once_with(
|
||||
self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test')
|
||||
add_devices.assert_called_once_with([mock_template.return_value])
|
||||
|
||||
def test_setup_no_sensors(self):
|
||||
""""Test setup with no sensors."""
|
||||
hass = mock.MagicMock()
|
||||
result = bootstrap.setup_component(hass, 'sensor', {
|
||||
result = bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
'platform': 'template'
|
||||
}
|
||||
|
@ -46,8 +56,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_setup_invalid_device(self):
|
||||
""""Test the setup with invalid devices."""
|
||||
hass = mock.MagicMock()
|
||||
result = bootstrap.setup_component(hass, 'sensor', {
|
||||
result = bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
|
@ -59,8 +68,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_setup_invalid_sensor_class(self):
|
||||
""""Test setup with invalid sensor class."""
|
||||
hass = mock.MagicMock()
|
||||
result = bootstrap.setup_component(hass, 'sensor', {
|
||||
result = bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
|
@ -75,8 +83,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_setup_invalid_missing_template(self):
|
||||
""""Test setup with invalid and missing template."""
|
||||
hass = mock.MagicMock()
|
||||
result = bootstrap.setup_component(hass, 'sensor', {
|
||||
result = bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
|
@ -90,9 +97,9 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_attributes(self):
|
||||
""""Test the attributes."""
|
||||
hass = mock.MagicMock()
|
||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||
'motion', '{{ 1 > 1 }}', MATCH_ALL)
|
||||
vs = template.BinarySensorTemplate(
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
self.assertFalse(vs.should_poll)
|
||||
self.assertEqual('motion', vs.sensor_class)
|
||||
self.assertEqual('Parent', vs.name)
|
||||
|
@ -100,32 +107,29 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
vs.update()
|
||||
self.assertFalse(vs.is_on)
|
||||
|
||||
vs._template = "{{ 2 > 1 }}"
|
||||
vs._template = template_hlpr.Template("{{ 2 > 1 }}", self.hass)
|
||||
vs.update()
|
||||
self.assertTrue(vs.is_on)
|
||||
|
||||
def test_event(self):
|
||||
""""Test the event."""
|
||||
hass = get_test_home_assistant()
|
||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||
'motion', '{{ 1 > 1 }}', MATCH_ALL)
|
||||
vs = template.BinarySensorTemplate(
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
vs.update_ha_state()
|
||||
hass.block_till_done()
|
||||
self.hass.block_till_done()
|
||||
|
||||
with mock.patch.object(vs, 'update') as mock_update:
|
||||
hass.bus.fire(EVENT_STATE_CHANGED)
|
||||
hass.block_till_done()
|
||||
try:
|
||||
assert mock_update.call_count == 1
|
||||
finally:
|
||||
hass.stop()
|
||||
self.hass.bus.fire(EVENT_STATE_CHANGED)
|
||||
self.hass.block_till_done()
|
||||
assert mock_update.call_count == 1
|
||||
|
||||
@mock.patch('homeassistant.helpers.template.render')
|
||||
@mock.patch('homeassistant.helpers.template.Template.render')
|
||||
def test_update_template_error(self, mock_render):
|
||||
""""Test the template update error."""
|
||||
hass = mock.MagicMock()
|
||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||
'motion', '{{ 1 > 1 }}', MATCH_ALL)
|
||||
vs = template.BinarySensorTemplate(
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
mock_render.side_effect = TemplateError('foo')
|
||||
vs.update()
|
||||
mock_render.side_effect = TemplateError(
|
||||
|
|
|
@ -118,20 +118,6 @@ class TestMQTT(unittest.TestCase):
|
|||
}, blocking=True)
|
||||
self.assertFalse(mqtt.MQTT_CLIENT.publish.called)
|
||||
|
||||
def test_service_call_without_payload_or_payload_template(self):
|
||||
"""Test the service call without payload or payload template.
|
||||
|
||||
Send empty message if neither 'payload' nor 'payload_template'
|
||||
are provided.
|
||||
"""
|
||||
# Call the service directly because the helper functions require you to
|
||||
# provide a payload.
|
||||
self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, {
|
||||
mqtt.ATTR_TOPIC: "test/topic"
|
||||
}, blocking=True)
|
||||
self.assertTrue(mqtt.MQTT_CLIENT.publish.called)
|
||||
self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "")
|
||||
|
||||
def test_service_call_with_ascii_qos_retain_flags(self):
|
||||
"""Test the service call with args that can be misinterpreted.
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""The tests for the Command line sensor platform."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.components.sensor import command_line
|
||||
from homeassistant import bootstrap
|
||||
from tests.common import get_test_home_assistant
|
||||
|
@ -53,7 +54,8 @@ class TestCommandSensorSensor(unittest.TestCase):
|
|||
data = command_line.CommandSensorData('echo 50')
|
||||
|
||||
entity = command_line.CommandSensor(
|
||||
self.hass, data, 'test', 'in', '{{ value | multiply(0.1) }}')
|
||||
self.hass, data, 'test', 'in',
|
||||
Template('{{ value | multiply(0.1) }}', self.hass))
|
||||
|
||||
self.assertEqual(5, float(entity.state))
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
"""The tests for the IMAP email content sensor platform."""
|
||||
import unittest
|
||||
from collections import deque
|
||||
import email
|
||||
import datetime
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import datetime
|
||||
from threading import Event
|
||||
import unittest
|
||||
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from collections import deque
|
||||
|
||||
from homeassistant.components.sensor import imap_email_content
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
|
@ -218,7 +218,8 @@ class EmailContentSensor(unittest.TestCase):
|
|||
FakeEMailReader(deque([test_message])),
|
||||
"test_emails_sensor",
|
||||
["sender@test.com"],
|
||||
"{{ subject }} from {{ from }} with message {{ body }}")
|
||||
Template("{{ subject }} from {{ from }} with message {{ body }}",
|
||||
self.hass))
|
||||
|
||||
sensor.entity_id = "sensor.emailtest"
|
||||
sensor.update()
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
from homeassistant.components import rfxtrx as rfxtrx
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
@pytest.mark.skipif("os.environ.get('RFXTRX') == 'SKIP'")
|
||||
class TestRFXTRX(unittest.TestCase):
|
||||
"""Test the Rfxtrx component."""
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# pylint: disable=too-many-public-methods,protected-access
|
||||
import unittest
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
from homeassistant.bootstrap import setup_component
|
||||
from homeassistant.components import script
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
@ -41,7 +41,7 @@ class TestScriptComponent(unittest.TestCase):
|
|||
}
|
||||
},
|
||||
):
|
||||
assert not _setup_component(self.hass, 'script', {
|
||||
assert not setup_component(self.hass, 'script', {
|
||||
'script': value
|
||||
}), 'Script loaded with wrong config {}'.format(value)
|
||||
|
||||
|
@ -58,7 +58,7 @@ class TestScriptComponent(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
assert _setup_component(self.hass, 'script', {
|
||||
assert setup_component(self.hass, 'script', {
|
||||
'script': {
|
||||
'test': {
|
||||
'sequence': [{
|
||||
|
@ -93,7 +93,7 @@ class TestScriptComponent(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
assert _setup_component(self.hass, 'script', {
|
||||
assert setup_component(self.hass, 'script', {
|
||||
'script': {
|
||||
'test': {
|
||||
'sequence': [{
|
||||
|
@ -127,7 +127,7 @@ class TestScriptComponent(unittest.TestCase):
|
|||
|
||||
self.hass.services.register('test', 'script', record_call)
|
||||
|
||||
assert _setup_component(self.hass, 'script', {
|
||||
assert setup_component(self.hass, 'script', {
|
||||
'script': {
|
||||
'test': {
|
||||
'sequence': {
|
||||
|
|
|
@ -5,7 +5,7 @@ import unittest
|
|||
from unittest.mock import patch
|
||||
from subprocess import SubprocessError
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
from homeassistant.bootstrap import setup_component
|
||||
from homeassistant.components import shell_command
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
@ -26,7 +26,7 @@ class TestShellCommand(unittest.TestCase):
|
|||
"""Test if able to call a configured service."""
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'called.txt')
|
||||
assert _setup_component(self.hass, shell_command.DOMAIN, {
|
||||
assert setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: {
|
||||
'test_service': "date > {}".format(path)
|
||||
}
|
||||
|
@ -40,41 +40,54 @@ class TestShellCommand(unittest.TestCase):
|
|||
|
||||
def test_config_not_dict(self):
|
||||
"""Test if config is not a dict."""
|
||||
assert not _setup_component(self.hass, shell_command.DOMAIN, {
|
||||
assert not setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: ['some', 'weird', 'list']
|
||||
})
|
||||
|
||||
def test_config_not_valid_service_names(self):
|
||||
"""Test if config contains invalid service names."""
|
||||
assert not _setup_component(self.hass, shell_command.DOMAIN, {
|
||||
assert not setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: {
|
||||
'this is invalid because space': 'touch bla.txt'
|
||||
}
|
||||
})
|
||||
|
||||
def test_template_render_no_template(self):
|
||||
@patch('homeassistant.components.shell_command.subprocess.call')
|
||||
def test_template_render_no_template(self, mock_call):
|
||||
"""Ensure shell_commands without templates get rendered properly."""
|
||||
cmd, shell = shell_command._parse_command(self.hass, 'ls /bin', {})
|
||||
self.assertTrue(shell)
|
||||
self.assertEqual(cmd, 'ls /bin')
|
||||
assert setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: {
|
||||
'test_service': "ls /bin"
|
||||
}
|
||||
})
|
||||
|
||||
def test_template_render(self):
|
||||
"""Ensure shell_commands with templates get rendered properly."""
|
||||
self.hass.services.call('shell_command', 'test_service',
|
||||
blocking=True)
|
||||
|
||||
cmd = mock_call.mock_calls[0][1][0]
|
||||
shell = mock_call.mock_calls[0][2]['shell']
|
||||
|
||||
assert 'ls /bin' == cmd
|
||||
assert shell
|
||||
|
||||
@patch('homeassistant.components.shell_command.subprocess.call')
|
||||
def test_template_render(self, mock_call):
|
||||
"""Ensure shell_commands without templates get rendered properly."""
|
||||
self.hass.states.set('sensor.test_state', 'Works')
|
||||
cmd, shell = shell_command._parse_command(
|
||||
self.hass,
|
||||
'ls /bin {{ states.sensor.test_state.state }}', {}
|
||||
)
|
||||
self.assertFalse(shell, False)
|
||||
self.assertEqual(cmd[-1], 'Works')
|
||||
assert setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: {
|
||||
'test_service': "ls /bin {{ states.sensor.test_state.state }}"
|
||||
}
|
||||
})
|
||||
|
||||
def test_invalid_template_fails(self):
|
||||
"""Test that shell_commands with invalid templates fail."""
|
||||
cmd, _shell = shell_command._parse_command(
|
||||
self.hass,
|
||||
'ls /bin {{ states. .test_state.state }}', {}
|
||||
)
|
||||
self.assertEqual(cmd, None)
|
||||
self.hass.services.call('shell_command', 'test_service',
|
||||
blocking=True)
|
||||
|
||||
cmd = mock_call.mock_calls[0][1][0]
|
||||
shell = mock_call.mock_calls[0][2]['shell']
|
||||
|
||||
assert ['ls', '/bin', 'Works'] == cmd
|
||||
assert not shell
|
||||
|
||||
@patch('homeassistant.components.shell_command.subprocess.call',
|
||||
side_effect=SubprocessError)
|
||||
|
@ -83,7 +96,7 @@ class TestShellCommand(unittest.TestCase):
|
|||
"""Test subprocess."""
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'called.txt')
|
||||
assert _setup_component(self.hass, shell_command.DOMAIN, {
|
||||
assert setup_component(self.hass, shell_command.DOMAIN, {
|
||||
shell_command.DOMAIN: {
|
||||
'test_service': "touch {}".format(path)
|
||||
}
|
||||
|
|
|
@ -44,6 +44,32 @@ class TestConditionHelper:
|
|||
self.hass.states.set('sensor.temperature', 100)
|
||||
assert test(self.hass)
|
||||
|
||||
def test_and_condition_with_template(self):
|
||||
"""Test the 'and' condition."""
|
||||
test = condition.from_config({
|
||||
'condition': 'and',
|
||||
'conditions': [
|
||||
{
|
||||
'condition': 'template',
|
||||
'value_template':
|
||||
'{{ states.sensor.temperature.state == "100" }}',
|
||||
}, {
|
||||
'condition': 'numeric_state',
|
||||
'entity_id': 'sensor.temperature',
|
||||
'below': 110,
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
self.hass.states.set('sensor.temperature', 120)
|
||||
assert not test(self.hass)
|
||||
|
||||
self.hass.states.set('sensor.temperature', 105)
|
||||
assert not test(self.hass)
|
||||
|
||||
self.hass.states.set('sensor.temperature', 100)
|
||||
assert test(self.hass)
|
||||
|
||||
def test_or_condition(self):
|
||||
"""Test the 'or' condition."""
|
||||
test = condition.from_config({
|
||||
|
@ -70,6 +96,32 @@ class TestConditionHelper:
|
|||
self.hass.states.set('sensor.temperature', 100)
|
||||
assert test(self.hass)
|
||||
|
||||
def test_or_condition_with_template(self):
|
||||
"""Test the 'or' condition."""
|
||||
test = condition.from_config({
|
||||
'condition': 'or',
|
||||
'conditions': [
|
||||
{
|
||||
'condition': 'template',
|
||||
'value_template':
|
||||
'{{ states.sensor.temperature.state == "100" }}',
|
||||
}, {
|
||||
'condition': 'numeric_state',
|
||||
'entity_id': 'sensor.temperature',
|
||||
'below': 110,
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
self.hass.states.set('sensor.temperature', 120)
|
||||
assert not test(self.hass)
|
||||
|
||||
self.hass.states.set('sensor.temperature', 105)
|
||||
assert test(self.hass)
|
||||
|
||||
self.hass.states.set('sensor.temperature', 100)
|
||||
assert test(self.hass)
|
||||
|
||||
def test_time_window(self):
|
||||
"""Test time condition windows."""
|
||||
sixam = dt.parse_time("06:00:00")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Test config validators."""
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import enum
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
@ -302,7 +303,8 @@ def test_template():
|
|||
schema = vol.Schema(cv.template)
|
||||
|
||||
for value in (None, '{{ partial_print }', '{% if True %}Hello', ['test']):
|
||||
with pytest.raises(vol.MultipleInvalid):
|
||||
with pytest.raises(vol.Invalid,
|
||||
message='{} not considered invalid'.format(value)):
|
||||
schema(value)
|
||||
|
||||
for value in (
|
||||
|
@ -417,3 +419,19 @@ def test_ordered_dict_value_validator():
|
|||
schema({'hello': 'world'})
|
||||
|
||||
schema({'hello': 5})
|
||||
|
||||
|
||||
def test_enum():
|
||||
"""Test enum validator."""
|
||||
class TestEnum(enum.Enum):
|
||||
"""Test enum."""
|
||||
|
||||
value1 = "Value 1"
|
||||
value2 = "Value 2"
|
||||
|
||||
schema = vol.Schema(cv.enum(TestEnum))
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
schema('value3')
|
||||
|
||||
TestEnum['value1']
|
||||
|
|
|
@ -6,7 +6,7 @@ import unittest
|
|||
# Otherwise can't test just this file (import order issue)
|
||||
import homeassistant.components # noqa
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers import script
|
||||
from homeassistant.helpers import script, config_validation as cv
|
||||
|
||||
from tests.common import fire_time_changed, get_test_home_assistant
|
||||
|
||||
|
@ -36,12 +36,12 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
script_obj = script.Script(self.hass, {
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA({
|
||||
'event': event,
|
||||
'event_data': {
|
||||
'hello': 'world'
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
script_obj.run()
|
||||
|
||||
|
@ -61,14 +61,13 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.services.register('test', 'script', record_call)
|
||||
|
||||
script_obj = script.Script(self.hass, {
|
||||
script.call_from_config(self.hass, {
|
||||
'service': 'test.script',
|
||||
'data': {
|
||||
'hello': 'world'
|
||||
}
|
||||
})
|
||||
|
||||
script_obj.run()
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
|
@ -84,7 +83,7 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.services.register('test', 'script', record_call)
|
||||
|
||||
script_obj = script.Script(self.hass, {
|
||||
script.call_from_config(self.hass, {
|
||||
'service_template': """
|
||||
{% if True %}
|
||||
test.script
|
||||
|
@ -102,8 +101,6 @@ class TestScriptHelper(unittest.TestCase):
|
|||
}
|
||||
})
|
||||
|
||||
script_obj.run()
|
||||
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
|
@ -120,10 +117,10 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
script_obj = script.Script(self.hass, [
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{'event': event},
|
||||
{'delay': {'seconds': 5}},
|
||||
{'event': event}])
|
||||
{'event': event}]))
|
||||
|
||||
script_obj.run()
|
||||
|
||||
|
@ -152,10 +149,10 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
script_obj = script.Script(self.hass, [
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{'event': event},
|
||||
{'delay': '00:00:{{ 5 }}'},
|
||||
{'event': event}])
|
||||
{'event': event}]))
|
||||
|
||||
script_obj.run()
|
||||
|
||||
|
@ -184,9 +181,9 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
script_obj = script.Script(self.hass, [
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{'delay': {'seconds': 5}},
|
||||
{'event': event}])
|
||||
{'event': event}]))
|
||||
|
||||
script_obj.run()
|
||||
|
||||
|
@ -217,7 +214,7 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.services.register('test', 'script', record_call)
|
||||
|
||||
script_obj = script.Script(self.hass, [
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{
|
||||
'service': 'test.script',
|
||||
'data_template': {
|
||||
|
@ -230,7 +227,7 @@ class TestScriptHelper(unittest.TestCase):
|
|||
'data_template': {
|
||||
'hello': '{{ greeting2 }}',
|
||||
},
|
||||
}])
|
||||
}]))
|
||||
|
||||
script_obj.run({
|
||||
'greeting': 'world',
|
||||
|
@ -264,14 +261,14 @@ class TestScriptHelper(unittest.TestCase):
|
|||
|
||||
self.hass.states.set('test.entity', 'hello')
|
||||
|
||||
script_obj = script.Script(self.hass, [
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{'event': event},
|
||||
{
|
||||
'condition': 'template',
|
||||
'value_template': '{{ states.test.entity.state == "hello" }}',
|
||||
},
|
||||
{'event': event},
|
||||
])
|
||||
]))
|
||||
|
||||
script_obj.run()
|
||||
self.hass.block_till_done()
|
||||
|
|
|
@ -12,13 +12,14 @@ from homeassistant.const import (
|
|||
TEMP_CELSIUS,
|
||||
MASS_GRAMS,
|
||||
VOLUME_LITERS,
|
||||
MATCH_ALL,
|
||||
)
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestUtilTemplate(unittest.TestCase):
|
||||
class TestHelpersTemplate(unittest.TestCase):
|
||||
"""Test the Template."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
|
@ -37,7 +38,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
self.hass.states.set('test.object', 'happy')
|
||||
self.assertEqual(
|
||||
'happy',
|
||||
template.render(self.hass, '{{ states.test.object.state }}'))
|
||||
template.Template(
|
||||
'{{ states.test.object.state }}', self.hass).render())
|
||||
|
||||
def test_iterating_all_states(self):
|
||||
"""Test iterating all states."""
|
||||
|
@ -46,9 +48,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'10happy',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{% for state in states %}{{ state.state }}{% endfor %}'))
|
||||
template.Template(
|
||||
'{% for state in states %}{{ state.state }}{% endfor %}',
|
||||
self.hass).render())
|
||||
|
||||
def test_iterating_domain_states(self):
|
||||
"""Test iterating domain states."""
|
||||
|
@ -58,11 +60,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'open10',
|
||||
template.render(
|
||||
self.hass,
|
||||
"""
|
||||
template.Template("""
|
||||
{% for state in states.sensor %}{{ state.state }}{% endfor %}
|
||||
"""))
|
||||
""", self.hass).render())
|
||||
|
||||
def test_float(self):
|
||||
"""Test float."""
|
||||
|
@ -70,15 +70,15 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'12.0',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ float(states.sensor.temperature.state) }}'))
|
||||
template.Template(
|
||||
'{{ float(states.sensor.temperature.state) }}',
|
||||
self.hass).render())
|
||||
|
||||
self.assertEqual(
|
||||
'True',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ float(states.sensor.temperature.state) > 11 }}'))
|
||||
template.Template(
|
||||
'{{ float(states.sensor.temperature.state) > 11 }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_rounding_value(self):
|
||||
"""Test rounding value."""
|
||||
|
@ -86,32 +86,26 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'12.8',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ states.sensor.temperature.state | round(1) }}'))
|
||||
template.Template(
|
||||
'{{ states.sensor.temperature.state | round(1) }}',
|
||||
self.hass).render())
|
||||
|
||||
self.assertEqual(
|
||||
'128',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ states.sensor.temperature.state | multiply(10) | round }}'
|
||||
))
|
||||
template.Template(
|
||||
'{{ states.sensor.temperature.state | multiply(10) | round }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_rounding_value_get_original_value_on_error(self):
|
||||
"""Test rounding value get original value on error."""
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ None | round }}'
|
||||
))
|
||||
template.Template('{{ None | round }}', self.hass).render())
|
||||
|
||||
self.assertEqual(
|
||||
'no_number',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ "no_number" | round }}'
|
||||
))
|
||||
template.Template(
|
||||
'{{ "no_number" | round }}', self.hass).render())
|
||||
|
||||
def test_multiply(self):
|
||||
"""Test multiply."""
|
||||
|
@ -124,8 +118,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
for inp, out in tests.items():
|
||||
self.assertEqual(
|
||||
out,
|
||||
template.render(self.hass,
|
||||
'{{ %s | multiply(10) | round }}' % inp))
|
||||
template.Template('{{ %s | multiply(10) | round }}' % inp,
|
||||
self.hass).render())
|
||||
|
||||
def test_timestamp_custom(self):
|
||||
"""Test the timestamps to custom filter."""
|
||||
|
@ -148,8 +142,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
out,
|
||||
template.render(self.hass, '{{ %s | %s }}' % (inp, fil))
|
||||
)
|
||||
template.Template('{{ %s | %s }}' % (inp, fil),
|
||||
self.hass).render())
|
||||
|
||||
def test_timestamp_local(self):
|
||||
"""Test the timestamps to local filter."""
|
||||
|
@ -161,8 +155,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
for inp, out in tests.items():
|
||||
self.assertEqual(
|
||||
out,
|
||||
template.render(self.hass,
|
||||
'{{ %s | timestamp_local }}' % inp))
|
||||
template.Template('{{ %s | timestamp_local }}' % inp,
|
||||
self.hass).render())
|
||||
|
||||
def test_timestamp_utc(self):
|
||||
"""Test the timestamps to local filter."""
|
||||
|
@ -176,112 +170,101 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
for inp, out in tests.items():
|
||||
self.assertEqual(
|
||||
out,
|
||||
template.render(self.hass,
|
||||
'{{ %s | timestamp_utc }}' % inp))
|
||||
template.Template('{{ %s | timestamp_utc }}' % inp,
|
||||
self.hass).render())
|
||||
|
||||
def test_passing_vars_as_keywords(self):
|
||||
"""Test passing variables as keywords."""
|
||||
self.assertEqual(
|
||||
'127', template.render(self.hass, '{{ hello }}', hello=127))
|
||||
'127',
|
||||
template.Template('{{ hello }}', self.hass).render(hello=127))
|
||||
|
||||
def test_passing_vars_as_vars(self):
|
||||
"""Test passing variables as variables."""
|
||||
self.assertEqual(
|
||||
'127', template.render(self.hass, '{{ hello }}', {'hello': 127}))
|
||||
'127',
|
||||
template.Template('{{ hello }}', self.hass).render({'hello': 127}))
|
||||
|
||||
def test_render_with_possible_json_value_with_valid_json(self):
|
||||
"""Render with possible JSON value with valid JSON."""
|
||||
tpl = template.Template('{{ value_json.hello }}', self.hass)
|
||||
self.assertEqual(
|
||||
'world',
|
||||
template.render_with_possible_json_value(
|
||||
self.hass, '{{ value_json.hello }}', '{"hello": "world"}'))
|
||||
tpl.render_with_possible_json_value('{"hello": "world"}'))
|
||||
|
||||
def test_render_with_possible_json_value_with_invalid_json(self):
|
||||
"""Render with possible JSON value with invalid JSON."""
|
||||
tpl = template.Template('{{ value_json }}', self.hass)
|
||||
self.assertEqual(
|
||||
'',
|
||||
template.render_with_possible_json_value(
|
||||
self.hass, '{{ value_json }}', '{ I AM NOT JSON }'))
|
||||
|
||||
def test_render_with_possible_json_value_with_template_error(self):
|
||||
"""Render with possible JSON value with template error."""
|
||||
self.assertEqual(
|
||||
'hello',
|
||||
template.render_with_possible_json_value(
|
||||
self.hass, '{{ value_json', 'hello'))
|
||||
tpl.render_with_possible_json_value('{ I AM NOT JSON }'))
|
||||
|
||||
def test_render_with_possible_json_value_with_template_error_value(self):
|
||||
"""Render with possible JSON value with template error value."""
|
||||
tpl = template.Template('{{ non_existing.variable }}', self.hass)
|
||||
self.assertEqual(
|
||||
'-',
|
||||
template.render_with_possible_json_value(
|
||||
self.hass, '{{ value_json', 'hello', '-'))
|
||||
tpl.render_with_possible_json_value('hello', '-'))
|
||||
|
||||
def test_raise_exception_on_error(self):
|
||||
"""Test raising an exception on error."""
|
||||
with self.assertRaises(TemplateError):
|
||||
template.render(self.hass, '{{ invalid_syntax')
|
||||
template.Template('{{ invalid_syntax').ensure_valid()
|
||||
|
||||
def test_if_state_exists(self):
|
||||
"""Test if state exists works."""
|
||||
self.hass.states.set('test.object', 'available')
|
||||
self.assertEqual(
|
||||
'exists',
|
||||
template.render(
|
||||
self.hass,
|
||||
"""
|
||||
{% if states.test.object %}exists{% else %}not exists{% endif %}
|
||||
"""))
|
||||
tpl = template.Template(
|
||||
'{% if states.test.object %}exists{% else %}not exists{% endif %}',
|
||||
self.hass)
|
||||
self.assertEqual('exists', tpl.render())
|
||||
|
||||
def test_is_state(self):
|
||||
"""Test is_state method."""
|
||||
self.hass.states.set('test.object', 'available')
|
||||
self.assertEqual(
|
||||
'yes',
|
||||
template.render(
|
||||
self.hass,
|
||||
"""
|
||||
tpl = template.Template("""
|
||||
{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}
|
||||
"""))
|
||||
""", self.hass)
|
||||
self.assertEqual('yes', tpl.render())
|
||||
|
||||
def test_is_state_attr(self):
|
||||
"""Test is_state_attr method."""
|
||||
self.hass.states.set('test.object', 'available', {'mode': 'on'})
|
||||
self.assertEqual(
|
||||
'yes',
|
||||
template.render(
|
||||
self.hass,
|
||||
"""
|
||||
tpl = template.Template("""
|
||||
{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %}
|
||||
"""))
|
||||
""", self.hass)
|
||||
self.assertEqual('yes', tpl.render())
|
||||
|
||||
def test_states_function(self):
|
||||
"""Test using states as a function."""
|
||||
self.hass.states.set('test.object', 'available')
|
||||
self.assertEqual(
|
||||
'available',
|
||||
template.render(self.hass, '{{ states("test.object") }}'))
|
||||
self.assertEqual(
|
||||
'unknown',
|
||||
template.render(self.hass, '{{ states("test.object2") }}'))
|
||||
tpl = template.Template('{{ states("test.object") }}', self.hass)
|
||||
self.assertEqual('available', tpl.render())
|
||||
|
||||
tpl2 = template.Template('{{ states("test.object2") }}', self.hass)
|
||||
self.assertEqual('unknown', tpl2.render())
|
||||
|
||||
@patch('homeassistant.core.dt_util.now', return_value=dt_util.now())
|
||||
@patch('homeassistant.helpers.template.TemplateEnvironment.'
|
||||
'is_safe_callable', return_value=True)
|
||||
def test_now(self, mock_is_safe, mock_utcnow):
|
||||
def test_now(self, mock_is_safe):
|
||||
"""Test now method."""
|
||||
self.assertEqual(
|
||||
dt_util.now().isoformat(),
|
||||
template.render(self.hass, '{{ now().isoformat() }}'))
|
||||
now = dt_util.now()
|
||||
with patch.dict(template.ENV.globals, {'now': lambda: now}):
|
||||
self.assertEqual(
|
||||
now.isoformat(),
|
||||
template.Template('{{ now().isoformat() }}',
|
||||
self.hass).render())
|
||||
|
||||
@patch('homeassistant.core.dt_util.utcnow', return_value=dt_util.utcnow())
|
||||
@patch('homeassistant.helpers.template.TemplateEnvironment.'
|
||||
'is_safe_callable', return_value=True)
|
||||
def test_utcnow(self, mock_is_safe, mock_utcnow):
|
||||
def test_utcnow(self, mock_is_safe):
|
||||
"""Test utcnow method."""
|
||||
self.assertEqual(
|
||||
dt_util.utcnow().isoformat(),
|
||||
template.render(self.hass, '{{ utcnow().isoformat() }}'))
|
||||
now = dt_util.utcnow()
|
||||
with patch.dict(template.ENV.globals, {'utcnow': lambda: now}):
|
||||
self.assertEqual(
|
||||
now.isoformat(),
|
||||
template.Template('{{ utcnow().isoformat() }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_distance_function_with_1_state(self):
|
||||
"""Test distance function with 1 state."""
|
||||
|
@ -289,11 +272,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
'latitude': 32.87336,
|
||||
'longitude': -117.22943,
|
||||
})
|
||||
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass, '{{ distance(states.test.object) | round }}'))
|
||||
tpl = template.Template('{{ distance(states.test.object) | round }}',
|
||||
self.hass)
|
||||
self.assertEqual('187', tpl.render())
|
||||
|
||||
def test_distance_function_with_2_states(self):
|
||||
"""Test distance function with 2 states."""
|
||||
|
@ -301,34 +282,31 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
'latitude': 32.87336,
|
||||
'longitude': -117.22943,
|
||||
})
|
||||
|
||||
self.hass.states.set('test.object_2', 'happy', {
|
||||
'latitude': self.hass.config.latitude,
|
||||
'longitude': self.hass.config.longitude,
|
||||
})
|
||||
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance(states.test.object, states.test.object_2)'
|
||||
'| round }}'))
|
||||
tpl = template.Template(
|
||||
'{{ distance(states.test.object, states.test.object_2) | round }}',
|
||||
self.hass)
|
||||
self.assertEqual('187', tpl.render())
|
||||
|
||||
def test_distance_function_with_1_coord(self):
|
||||
"""Test distance function with 1 coord."""
|
||||
tpl = template.Template(
|
||||
'{{ distance("32.87336", "-117.22943") | round }}', self.hass)
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass, '{{ distance("32.87336", "-117.22943") | round }}'))
|
||||
tpl.render())
|
||||
|
||||
def test_distance_function_with_2_coords(self):
|
||||
"""Test distance function with 2 coords."""
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass,
|
||||
template.Template(
|
||||
'{{ distance("32.87336", "-117.22943", %s, %s) | round }}'
|
||||
% (self.hass.config.latitude, self.hass.config.longitude)))
|
||||
% (self.hass.config.latitude, self.hass.config.longitude),
|
||||
self.hass).render())
|
||||
|
||||
def test_distance_function_with_1_state_1_coord(self):
|
||||
"""Test distance function with 1 state 1 coord."""
|
||||
|
@ -336,57 +314,47 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
'latitude': self.hass.config.latitude,
|
||||
'longitude': self.hass.config.longitude,
|
||||
})
|
||||
tpl = template.Template(
|
||||
'{{ distance("32.87336", "-117.22943", states.test.object_2) '
|
||||
'| round }}', self.hass)
|
||||
self.assertEqual('187', tpl.render())
|
||||
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance("32.87336", "-117.22943", states.test.object_2) '
|
||||
'| round }}'))
|
||||
|
||||
self.assertEqual(
|
||||
'187',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance(states.test.object_2, "32.87336", "-117.22943") '
|
||||
'| round }}'))
|
||||
tpl2 = template.Template(
|
||||
'{{ distance(states.test.object_2, "32.87336", "-117.22943") '
|
||||
'| round }}', self.hass)
|
||||
self.assertEqual('187', tpl2.render())
|
||||
|
||||
def test_distance_function_return_None_if_invalid_state(self):
|
||||
"""Test distance function return None if invalid state."""
|
||||
self.hass.states.set('test.object_2', 'happy', {
|
||||
'latitude': 10,
|
||||
})
|
||||
|
||||
tpl = template.Template('{{ distance(states.test.object_2) | round }}',
|
||||
self.hass)
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance(states.test.object_2) | round }}'))
|
||||
tpl.render())
|
||||
|
||||
def test_distance_function_return_None_if_invalid_coord(self):
|
||||
"""Test distance function return None if invalid coord."""
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance("123", "abc") }}'))
|
||||
template.Template(
|
||||
'{{ distance("123", "abc") }}', self.hass).render())
|
||||
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance("123") }}'))
|
||||
template.Template('{{ distance("123") }}', self.hass).render())
|
||||
|
||||
self.hass.states.set('test.object_2', 'happy', {
|
||||
'latitude': self.hass.config.latitude,
|
||||
'longitude': self.hass.config.longitude,
|
||||
})
|
||||
|
||||
tpl = template.Template('{{ distance("123", states.test_object_2) }}',
|
||||
self.hass)
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ distance("123", states.test_object_2) }}'))
|
||||
tpl.render())
|
||||
|
||||
def test_closest_function_home_vs_domain(self):
|
||||
"""Test closest function home vs domain."""
|
||||
|
@ -402,8 +370,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain.object',
|
||||
template.render(self.hass,
|
||||
'{{ closest(states.test_domain).entity_id }}'))
|
||||
template.Template('{{ closest(states.test_domain).entity_id }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_home_vs_all_states(self):
|
||||
"""Test closest function home vs all states."""
|
||||
|
@ -419,8 +387,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain_2.and_closer',
|
||||
template.render(self.hass,
|
||||
'{{ closest(states).entity_id }}'))
|
||||
template.Template('{{ closest(states).entity_id }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_home_vs_group_entity_id(self):
|
||||
"""Test closest function home vs group entity id."""
|
||||
|
@ -438,8 +406,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain.object',
|
||||
template.render(self.hass,
|
||||
'{{ closest("group.location_group").entity_id }}'))
|
||||
template.Template(
|
||||
'{{ closest("group.location_group").entity_id }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_home_vs_group_state(self):
|
||||
"""Test closest function home vs group state."""
|
||||
|
@ -457,9 +426,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain.object',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ closest(states.group.location_group).entity_id }}'))
|
||||
template.Template(
|
||||
'{{ closest(states.group.location_group).entity_id }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_to_coord(self):
|
||||
"""Test closest function to coord."""
|
||||
|
@ -478,14 +447,14 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
'longitude': self.hass.config.longitude + 0.3,
|
||||
})
|
||||
|
||||
tpl = template.Template(
|
||||
'{{ closest("%s", %s, states.test_domain).entity_id }}'
|
||||
% (self.hass.config.latitude + 0.3,
|
||||
self.hass.config.longitude + 0.3), self.hass)
|
||||
|
||||
self.assertEqual(
|
||||
'test_domain.closest_zone',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ closest("%s", %s, states.test_domain).entity_id }}'
|
||||
% (self.hass.config.latitude + 0.3,
|
||||
self.hass.config.longitude + 0.3))
|
||||
)
|
||||
tpl.render())
|
||||
|
||||
def test_closest_function_to_entity_id(self):
|
||||
"""Test closest function to entity id."""
|
||||
|
@ -506,10 +475,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain.closest_zone',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{{ closest("zone.far_away", states.test_domain).entity_id }}')
|
||||
)
|
||||
template.Template(
|
||||
'{{ closest("zone.far_away", '
|
||||
'states.test_domain).entity_id }}', self.hass).render())
|
||||
|
||||
def test_closest_function_to_state(self):
|
||||
"""Test closest function to state."""
|
||||
|
@ -530,11 +498,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'test_domain.closest_zone',
|
||||
template.render(
|
||||
self.hass,
|
||||
template.Template(
|
||||
'{{ closest(states.zone.far_away, '
|
||||
'states.test_domain).entity_id }}')
|
||||
)
|
||||
'states.test_domain).entity_id }}', self.hass).render())
|
||||
|
||||
def test_closest_function_invalid_state(self):
|
||||
"""Test closest function invalid state."""
|
||||
|
@ -546,8 +512,8 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
for state in ('states.zone.non_existing', '"zone.non_existing"'):
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass, '{{ closest(%s, states) }}' % state))
|
||||
template.Template('{{ closest(%s, states) }}' % state,
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_state_with_invalid_location(self):
|
||||
"""Test closest function state with invalid location."""
|
||||
|
@ -558,10 +524,9 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(
|
||||
self.hass,
|
||||
template.Template(
|
||||
'{{ closest(states.test_domain.closest_home, '
|
||||
'states) }}'))
|
||||
'states) }}', self.hass).render())
|
||||
|
||||
def test_closest_function_invalid_coordinates(self):
|
||||
"""Test closest function invalid coordinates."""
|
||||
|
@ -572,20 +537,96 @@ class TestUtilTemplate(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.render(self.hass,
|
||||
'{{ closest("invalid", "coord", states) }}'))
|
||||
template.Template('{{ closest("invalid", "coord", states) }}',
|
||||
self.hass).render())
|
||||
|
||||
def test_closest_function_no_location_states(self):
|
||||
"""Test closest function without location states."""
|
||||
self.assertEqual('None',
|
||||
template.render(self.hass, '{{ closest(states) }}'))
|
||||
self.assertEqual(
|
||||
'None',
|
||||
template.Template('{{ closest(states) }}', self.hass).render())
|
||||
|
||||
def test_compiling_template(self):
|
||||
"""Test compiling a template."""
|
||||
self.hass.states.set('test_domain.hello', 'world')
|
||||
compiled = template.compile_template(
|
||||
self.hass, '{{ states.test_domain.hello.state }}')
|
||||
def test_extract_entities_none_exclude_stuff(self):
|
||||
"""Test extract entities function with none or exclude stuff."""
|
||||
self.assertEqual(MATCH_ALL, template.extract_entities(None))
|
||||
|
||||
with patch('homeassistant.helpers.template.compile_template',
|
||||
side_effect=Exception('Should not be called')):
|
||||
assert 'world' == template.render(self.hass, compiled)
|
||||
self.assertEqual(
|
||||
MATCH_ALL,
|
||||
template.extract_entities(
|
||||
'{{ closest(states.zone.far_away, '
|
||||
'states.test_domain).entity_id }}'))
|
||||
|
||||
self.assertEqual(
|
||||
MATCH_ALL,
|
||||
template.extract_entities(
|
||||
'{{ distance("123", states.test_object_2) }}'))
|
||||
|
||||
def test_extract_entities_no_match_entities(self):
|
||||
"""Test extract entities function with none entities stuff."""
|
||||
self.assertEqual(
|
||||
MATCH_ALL,
|
||||
template.extract_entities(
|
||||
"{{ value_json.tst | timestamp_custom('%Y' True) }}"))
|
||||
|
||||
self.assertEqual(
|
||||
MATCH_ALL,
|
||||
template.extract_entities("""
|
||||
{% for state in states.sensor %}
|
||||
{{ state.entity_id }}={{ state.state }},
|
||||
{% endfor %}
|
||||
"""))
|
||||
|
||||
def test_extract_entities_match_entities(self):
|
||||
"""Test extract entities function with entities stuff."""
|
||||
self.assertListEqual(
|
||||
['device_tracker.phone_1'],
|
||||
template.extract_entities("""
|
||||
{% if is_state('device_tracker.phone_1', 'home') %}
|
||||
Ha, Hercules is home!
|
||||
{% else %}
|
||||
Hercules is at {{ states('device_tracker.phone_1') }}.
|
||||
{% endif %}
|
||||
"""))
|
||||
|
||||
self.assertListEqual(
|
||||
['binary_sensor.garage_door'],
|
||||
template.extract_entities("""
|
||||
{{ as_timestamp(states.binary_sensor.garage_door.last_changed) }}
|
||||
"""))
|
||||
|
||||
self.assertListEqual(
|
||||
['binary_sensor.garage_door'],
|
||||
template.extract_entities("""
|
||||
{{ states("binary_sensor.garage_door") }}
|
||||
"""))
|
||||
|
||||
self.assertListEqual(
|
||||
['device_tracker.phone_2'],
|
||||
template.extract_entities("""
|
||||
is_state_attr('device_tracker.phone_2', 'battery', 40)
|
||||
"""))
|
||||
|
||||
self.assertListEqual(
|
||||
sorted([
|
||||
'device_tracker.phone_1',
|
||||
'device_tracker.phone_2',
|
||||
]),
|
||||
sorted(template.extract_entities("""
|
||||
{% if is_state('device_tracker.phone_1', 'home') %}
|
||||
Ha, Hercules is home!
|
||||
{% elif states.device_tracker.phone_2.attributes.battery < 40 %}
|
||||
Hercules you power goes done!.
|
||||
{% endif %}
|
||||
""")))
|
||||
|
||||
self.assertListEqual(
|
||||
sorted([
|
||||
'sensor.pick_humidity',
|
||||
'sensor.pick_temperature',
|
||||
]),
|
||||
sorted(template.extract_entities("""
|
||||
{{
|
||||
states.sensor.pick_temperature.state ~ „°C (“ ~
|
||||
states.sensor.pick_humidity.state ~ „ %“
|
||||
}}
|
||||
""")))
|
||||
|
|
Loading…
Reference in New Issue