diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index c62fe9e7d6b..464355d6fd5 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -11,12 +11,12 @@ from types import ModuleType from typing import Any, Optional, Dict import voluptuous as vol +from voluptuous.humanize import humanize_error import homeassistant.components as core_components from homeassistant.components import group, persistent_notification import homeassistant.config as conf_util import homeassistant.core as core -import homeassistant.helpers.config_validation as cv import homeassistant.loader as loader import homeassistant.util.package as pkg_util from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT @@ -103,7 +103,7 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: try: config = component.CONFIG_SCHEMA(config) except vol.MultipleInvalid as ex: - cv.log_exception(_LOGGER, ex, domain, config) + _log_exception(ex, domain, config) return False elif hasattr(component, 'PLATFORM_SCHEMA'): @@ -113,7 +113,7 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: try: p_validated = component.PLATFORM_SCHEMA(p_config) except vol.MultipleInvalid as ex: - cv.log_exception(_LOGGER, ex, domain, p_config) + _log_exception(ex, domain, p_config) return False # Not all platform components follow same pattern for platforms @@ -134,8 +134,8 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: try: p_validated = platform.PLATFORM_SCHEMA(p_validated) except vol.MultipleInvalid as ex: - cv.log_exception(_LOGGER, ex, '{}.{}' - .format(domain, p_name), p_validated) + _log_exception(ex, '{}.{}'.format(domain, p_name), + p_validated) return False platforms.append(p_validated) @@ -239,7 +239,7 @@ def from_config_dict(config: Dict[str, Any], try: conf_util.process_ha_core_config(hass, core_config) except vol.Invalid as ex: - cv.log_exception(_LOGGER, ex, 'homeassistant', core_config) + _log_exception(ex, 'homeassistant', core_config) return None conf_util.process_ha_config_upgrade(hass) @@ -374,3 +374,20 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None: def _mount_local_lib_path(config_dir: str) -> None: """Add local library to Python Path.""" sys.path.insert(0, os.path.join(config_dir, 'deps')) + + +def _log_exception(ex, domain, config): + """Generate log exception for config validation.""" + message = 'Invalid config for [{}]: '.format(domain) + if 'extra keys not allowed' in ex.error_message: + message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ + .format(ex.path[-1], domain, domain, + '->'.join('%s' % m for m in ex.path)) + else: + message += humanize_error(config, ex) + + if hasattr(config, '__line__'): + message += " (See {}:{})".format(config.__config_file__, + config.__line__ or '?') + + _LOGGER.error(message) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 5b1c103a1bf..4b73c46b198 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -44,16 +44,19 @@ NOTIFY_SERVICE_SCHEMA = vol.Schema({ _LOGGER = logging.getLogger(__name__) -def send_message(hass, message, title=None): +def send_message(hass, message, title=None, data=None): """Send a notification message.""" - data = { + info = { ATTR_MESSAGE: message } if title is not None: - data[ATTR_TITLE] = title + info[ATTR_TITLE] = title - hass.services.call(DOMAIN, SERVICE_NOTIFY, data) + if data is not None: + info[ATTR_DATA] = data + + hass.services.call(DOMAIN, SERVICE_NOTIFY, info) def setup(hass, config): diff --git a/homeassistant/components/notify/demo.py b/homeassistant/components/notify/demo.py index c051bce020d..4685b90e880 100644 --- a/homeassistant/components/notify/demo.py +++ b/homeassistant/components/notify/demo.py @@ -4,7 +4,7 @@ Demo notification service. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ -from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService +from homeassistant.components.notify import BaseNotificationService EVENT_NOTIFY = "notify" @@ -24,5 +24,5 @@ class DemoNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" - title = kwargs.get(ATTR_TITLE) - self.hass.bus.fire(EVENT_NOTIFY, {"title": title, "message": message}) + kwargs['message'] = message + self.hass.bus.fire(EVENT_NOTIFY, kwargs) diff --git a/homeassistant/core.py b/homeassistant/core.py index 63273ce789a..ccd8a971f61 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -19,6 +19,7 @@ from types import MappingProxyType from typing import Optional, Any, Callable, List # NOQA import voluptuous as vol +from voluptuous.humanize import humanize_error from homeassistant.const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, @@ -571,7 +572,8 @@ class Service(object): self.func(call) except vol.MultipleInvalid as ex: _LOGGER.error('Invalid service data for %s.%s: %s', - call.domain, call.service, ex) + call.domain, call.service, + humanize_error(call.data, ex)) # pylint: disable=too-few-public-methods diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 2c4ed6795e9..91a05b37b5d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -152,23 +152,6 @@ def time_period_str(value: str) -> timedelta: time_period = vol.Any(time_period_str, timedelta, time_period_dict) -def log_exception(logger, ex, domain, config): - """Generate log exception for config validation.""" - message = 'Invalid config for [{}]: '.format(domain) - if 'extra keys not allowed' in ex.error_message: - message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ - .format(ex.path[-1], domain, domain, - '->'.join('%s' % m for m in ex.path)) - else: - message += str(ex) - - if hasattr(config, '__line__'): - message += " (See {}:{})".format(config.__config_file__, - config.__line__ or '?') - - logger.error(message) - - def match_all(value): """Validator that matches all values.""" return value @@ -239,6 +222,8 @@ def template(value): """Validate a jinja2 template.""" if value is None: raise vol.Invalid('template value is None') + if isinstance(value, (list, dict)): + raise vol.Invalid('template value should be a string') value = str(value) try: diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 0d4f2115ca7..3f7ffb576ed 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -1,8 +1,11 @@ """The tests for the notify demo platform.""" +import tempfile import unittest import homeassistant.components.notify as notify from homeassistant.components.notify import demo +from homeassistant.helpers import script +from homeassistant.util import yaml from tests.common import get_test_home_assistant @@ -45,3 +48,48 @@ class TestNotifyDemo(unittest.TestCase): last_event = self.events[-1] self.assertEqual(last_event.data[notify.ATTR_TITLE], 'temperature') self.assertEqual(last_event.data[notify.ATTR_MESSAGE], '10') + + def test_method_forwards_correct_data(self): + """Test that all data from the service gets forwarded to service.""" + notify.send_message(self.hass, 'my message', 'my title', + {'hello': 'world'}) + self.hass.pool.block_till_done() + self.assertTrue(len(self.events) == 1) + data = self.events[0].data + assert { + 'message': 'my message', + 'target': None, + 'title': 'my title', + 'data': {'hello': 'world'} + } == data + + def test_calling_notify_from_script_loaded_from_yaml(self): + """Test if we can call a notify from a script.""" + yaml_conf = """ +service: notify.notify +data: + data: + push: + sound: US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav +data_template: + message: > + Test 123 {{ 2 + 2 }} +""" + + with tempfile.NamedTemporaryFile() as fp: + fp.write(yaml_conf.encode('utf-8')) + fp.flush() + conf = yaml.load_yaml(fp.name) + + script.call_from_config(self.hass, conf) + self.hass.pool.block_till_done() + self.assertTrue(len(self.events) == 1) + assert { + 'message': 'Test 123 4', + 'target': None, + 'title': 'Home Assistant', + 'data': { + 'push': { + 'sound': + 'US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav'}} + } == self.events[0].data diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index b73dc6d6f94..7f94ab53b23 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -253,7 +253,7 @@ def test_template(): schema = vol.Schema(cv.template) for value in ( - None, '{{ partial_print }', '{% if True %}Hello' + None, '{{ partial_print }', '{% if True %}Hello', {'dict': 'isbad'} ): with pytest.raises(vol.MultipleInvalid): schema(value)