Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented event_data_template (new) #11057

Merged
merged 11 commits into from
Jan 19, 2018
1 change: 1 addition & 0 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ def validator(value):
vol.Optional(CONF_ALIAS): string,
vol.Required('event'): string,
vol.Optional('event_data'): dict,
vol.Optional('event_data_template'): {match_all: template_complex}
})

SERVICE_SCHEMA = vol.All(vol.Schema({
Expand Down
19 changes: 15 additions & 4 deletions homeassistant/helpers/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

from homeassistant.core import HomeAssistant, callback
from homeassistant.const import CONF_CONDITION, CONF_TIMEOUT
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import (
service, condition, template, config_validation as cv)
service, condition, template as template,
config_validation as cv)
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_template)
from homeassistant.helpers.typing import ConfigType
Expand All @@ -25,6 +27,7 @@
CONF_SEQUENCE = 'sequence'
CONF_EVENT = 'event'
CONF_EVENT_DATA = 'event_data'
CONF_EVENT_DATA_TEMPLATE = 'event_data_template'
CONF_DELAY = 'delay'
CONF_WAIT_TEMPLATE = 'wait_template'

Expand Down Expand Up @@ -145,7 +148,7 @@ def async_script_wait(entity_id, from_s, to_s):
break

elif CONF_EVENT in action:
self._async_fire_event(action)
self._async_fire_event(action, variables)

else:
yield from self._async_call_service(action, variables)
Expand Down Expand Up @@ -180,12 +183,20 @@ def _async_call_service(self, action, variables):
yield from service.async_call_from_config(
self.hass, action, True, variables, validate_config=False)

def _async_fire_event(self, action):
def _async_fire_event(self, action, variables):
"""Fire an event."""
self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT])
self._log("Executing step %s" % self.last_action)
event_data = dict(action.get(CONF_EVENT_DATA, {}))
if CONF_EVENT_DATA_TEMPLATE in action:
try:
event_data.update(template.render_complex(
action[CONF_EVENT_DATA_TEMPLATE], variables))
except TemplateError as ex:
_LOGGER.error('Error rendering event data template: %s', ex)

self.hass.bus.async_fire(action[CONF_EVENT],
action.get(CONF_EVENT_DATA))
event_data)

def _async_check_condition(self, action, variables):
"""Test if condition is matching."""
Expand Down
18 changes: 7 additions & 11 deletions homeassistant/helpers/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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, bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
Expand Down Expand Up @@ -63,17 +64,12 @@ def async_call_from_config(hass, config, blocking=False, variables=None,
service_data = dict(config.get(CONF_SERVICE_DATA, {}))

if CONF_SERVICE_DATA_TEMPLATE in config:
def _data_template_creator(value):
"""Recursive template creator helper function."""
if isinstance(value, list):
return [_data_template_creator(item) for item in value]
elif isinstance(value, dict):
return {key: _data_template_creator(item)
for key, item in value.items()}
value.hass = hass
return value.async_render(variables)
service_data.update(_data_template_creator(
config[CONF_SERVICE_DATA_TEMPLATE]))
try:
template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE])
service_data.update(template.render_complex(
config[CONF_SERVICE_DATA_TEMPLATE], variables))
except TemplateError as ex:
_LOGGER.error('Error rendering data template: %s', ex)

if CONF_SERVICE_ENTITY_ID in config:
service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID]
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/helpers/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ def attach(hass, obj):
obj.hass = hass


def render_complex(value, variables=None):
"""Recursive template creator helper function."""
if isinstance(value, list):
return [render_complex(item, variables)
for item in value]
elif isinstance(value, dict):
return {key: render_complex(item, variables)
for key, item in value.items()}
return value.async_render(variables)


def extract_entities(template, variables=None):
"""Extract all entities for state_changed listener from template string."""
if template is None or _RE_NONE_ENTITIES.search(template):
Expand Down
41 changes: 37 additions & 4 deletions tests/helpers/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,39 @@ def record_event(event):
assert calls[0].data.get('hello') == 'world'
assert not script_obj.can_cancel

def test_firing_event_template(self):
"""Test the firing of events."""
event = 'test_event'
calls = []

@callback
def record_event(event):
"""Add recorded event to set."""
calls.append(event)

self.hass.bus.listen(event, record_event)

script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA({
'event': event,
'event_data_template': {
'hello': """
{% if is_world == 'yes' %}
world
{% else %}
not world
{% endif %}
"""
}
}))

script_obj.run({'is_world': 'yes'})

self.hass.block_till_done()

assert len(calls) == 1
assert calls[0].data.get('hello') == 'world'
assert not script_obj.can_cancel

def test_calling_service(self):
"""Test the calling of a service."""
calls = []
Expand Down Expand Up @@ -99,14 +132,14 @@ def record_call(service):
{% endif %}""",
'data_template': {
'hello': """
{% if True %}
{% if is_world == 'yes' %}
world
{% else %}
Not world
not world
{% endif %}
"""
}
})
}, {'is_world': 'yes'})

self.hass.block_till_done()

Expand Down Expand Up @@ -147,7 +180,7 @@ def record_event(event):

def test_delay_template(self):
"""Test the delay as a template."""
event = 'test_evnt'
event = 'test_event'
events = []

@callback
Expand Down
30 changes: 30 additions & 0 deletions tests/helpers/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,36 @@ def test_passing_vars_as_vars(self):
'127',
template.Template('{{ hello }}', self.hass).render({'hello': 127}))

def test_passing_vars_as_list(self):
"""Test passing variables as list."""
self.assertEqual(
"['foo', 'bar']",
template.render_complex(template.Template('{{ hello }}',
self.hass), {'hello': ['foo', 'bar']}))

def test_passing_vars_as_list_element(self):
"""Test passing variables as list."""
self.assertEqual(
'bar',
template.render_complex(template.Template('{{ hello[1] }}',
self.hass),
{'hello': ['foo', 'bar']}))

def test_passing_vars_as_dict_element(self):
"""Test passing variables as list."""
self.assertEqual(
'bar',
template.render_complex(template.Template('{{ hello.foo }}',
self.hass),
{'hello': {'foo': 'bar'}}))

def test_passing_vars_as_dict(self):
"""Test passing variables as list."""
self.assertEqual(
"{'foo': 'bar'}",
template.render_complex(template.Template('{{ hello }}',
self.hass), {'hello': {'foo': 'bar'}}))

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)
Expand Down