From 412b5350ce3c16c55c6625e772f560aba351364b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 30 Sep 2016 23:26:15 -0700 Subject: [PATCH] Service config calls will no longer mutate original config (#3628) --- homeassistant/helpers/service.py | 26 +++++++++++--------------- homeassistant/helpers/template.py | 6 ++++++ tests/helpers/test_service.py | 29 +++++++++++++++++------------ 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 092d5983308..665e22404c6 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -62,22 +62,18 @@ def call_from_config(hass, config, blocking=False, variables=None, domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) - def _data_template_creator(value): - """Recursive template creator helper function.""" - if isinstance(value, list): - for idx, element in enumerate(value): - value[idx] = _data_template_creator(element) - return value - if isinstance(value, dict): - for key, element in value.items(): - value[key] = _data_template_creator(element) - return value - value.hass = hass - return value.render(variables) - if CONF_SERVICE_DATA_TEMPLATE in config: - for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items(): - service_data[key] = _data_template_creator(value) + 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.render(variables) + service_data.update(_data_template_creator( + config[CONF_SERVICE_DATA_TEMPLATE])) if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 6193d7f9ab8..03029c369e6 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -159,6 +159,12 @@ class Template(object): return self._compiled + def __eq__(self, other): + """Compare template with another.""" + return (self.__class__ == other.__class__ and + self.template == other.template and + self.hass == other.hass) + class AllStates(object): """Class to expose all HA states as attributes.""" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index d9fe3ff9c15..38af2178340 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -1,4 +1,5 @@ """Test service helpers.""" +from copy import deepcopy import unittest from unittest.mock import patch @@ -6,7 +7,8 @@ from unittest.mock import patch import homeassistant.components # noqa from homeassistant import core as ha, loader from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID -from homeassistant.helpers import service +from homeassistant.helpers import service, template +import homeassistant.helpers.config_validation as cv from tests.common import get_test_home_assistant, mock_service @@ -97,22 +99,25 @@ class TestServiceHelpers(unittest.TestCase): def test_not_mutate_input(self): """Test for immutable input.""" - orig = { + config = cv.SERVICE_SCHEMA({ 'service': 'test_domain.test_service', 'entity_id': 'hello.world, sensor.beer', 'data': { 'hello': 1, }, - } - service.call_from_config(self.hass, orig) - self.hass.block_till_done() - self.assertEqual({ - 'service': 'test_domain.test_service', - 'entity_id': 'hello.world, sensor.beer', - 'data': { - 'hello': 1, - }, - }, orig) + 'data_template': { + 'nested': { + 'value': '{{ 1 + 1 }}' + } + } + }) + orig = deepcopy(config) + + # Only change after call is each template getting hass attached + template.attach(self.hass, orig) + + service.call_from_config(self.hass, config, validate_config=False) + assert orig == config @patch('homeassistant.helpers.service._LOGGER.error') def test_fail_silently_if_no_service(self, mock_log):