From 848781fbb757449f8159ec14b1aa3055560d5e06 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Tue, 16 Aug 2016 22:14:04 -0700 Subject: [PATCH] Add a group notify platform (#2842) * Add a group notify platform which allows sending a single notification to multiple platforms. * Correctly sort group.py * Clean up the payload logic * Make name and entity id required in the schema * Deep update the dictionary to fix a bug where data wasnt merging. * Add notify.group tests. * Improve docstrings. * Change entities to services and entity_id to service * Make service a slug without a default value * Update tests for entities->services, entity_id->service * vol.Any(cv.slug) -> cv.slug --- .coveragerc | 1 + homeassistant/components/notify/group.py | 65 +++++++++++++++++++ tests/components/notify/test_group.py | 82 ++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 homeassistant/components/notify/group.py create mode 100644 tests/components/notify/test_group.py diff --git a/.coveragerc b/.coveragerc index 9c8a4695537..acb72210603 100644 --- a/.coveragerc +++ b/.coveragerc @@ -173,6 +173,7 @@ omit = homeassistant/components/notify/aws_sqs.py homeassistant/components/notify/free_mobile.py homeassistant/components/notify/gntp.py + homeassistant/components/notify/group.py homeassistant/components/notify/instapush.py homeassistant/components/notify/joaoapps_join.py homeassistant/components/notify/message_bird.py diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/notify/group.py new file mode 100644 index 00000000000..522b231d8cf --- /dev/null +++ b/homeassistant/components/notify/group.py @@ -0,0 +1,65 @@ +""" +Group platform for notify component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.group/ +""" +import collections +import logging +import voluptuous as vol + +from homeassistant.const import (CONF_PLATFORM, CONF_NAME, ATTR_SERVICE) +from homeassistant.components.notify import (DOMAIN, ATTR_MESSAGE, ATTR_DATA, + BaseNotificationService) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_SERVICES = "services" + +PLATFORM_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): "group", + vol.Required(CONF_NAME): vol.Coerce(str), + vol.Required(CONF_SERVICES): vol.All(cv.ensure_list, [{ + vol.Required(ATTR_SERVICE): cv.slug, + vol.Optional(ATTR_DATA): dict, + }]) +}) + + +def update(input_dict, update_source): + """Deep update a dictionary.""" + for key, val in update_source.items(): + if isinstance(val, collections.Mapping): + recurse = update(input_dict.get(key, {}), val) + input_dict[key] = recurse + else: + input_dict[key] = update_source[key] + return input_dict + + +def get_service(hass, config): + """Get the Group notification service.""" + return GroupNotifyPlatform(hass, config.get(CONF_SERVICES)) + + +# pylint: disable=too-few-public-methods +class GroupNotifyPlatform(BaseNotificationService): + """Implement the notification service for the group notify playform.""" + + def __init__(self, hass, entities): + """Initialize the service.""" + self.hass = hass + self.entities = entities + + def send_message(self, message="", **kwargs): + """Send message to all entities in the group.""" + payload = {ATTR_MESSAGE: message} + payload.update({key: val for key, val in kwargs.items() if val}) + + for entity in self.entities: + sending_payload = payload.copy() + if entity.get(ATTR_DATA) is not None: + update(sending_payload, entity.get(ATTR_DATA)) + self.hass.services.call(DOMAIN, entity.get(ATTR_SERVICE), + sending_payload) diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py new file mode 100644 index 00000000000..20e2259ca6e --- /dev/null +++ b/tests/components/notify/test_group.py @@ -0,0 +1,82 @@ +"""The tests for the notify.group platform.""" +import unittest + +import homeassistant.components.notify as notify +from homeassistant.components.notify import group + +from tests.common import get_test_home_assistant + + +class TestNotifyGroup(unittest.TestCase): + """Test the notify.group platform.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.events = [] + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'demo1', + 'platform': 'demo' + } + })) + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'demo2', + 'platform': 'demo' + } + })) + + self.service = group.get_service(self.hass, {'services': [ + {'service': 'demo1'}, + {'service': 'demo2', + 'data': {'target': 'unnamed device', + 'data': {'test': 'message'}}}]}) + + assert self.service is not None + + def record_event(event): + """Record event to send notification.""" + self.events.append(event) + + self.hass.bus.listen("notify", record_event) + + def tearDown(self): # pylint: disable=invalid-name + """"Stop everything that was started.""" + self.hass.stop() + + def test_send_message_to_group(self): + """Test sending a message to a notify group.""" + self.service.send_message('Hello', title='Test notification') + self.hass.pool.block_till_done() + self.assertTrue(len(self.events) == 2) + last_event = self.events[-1] + self.assertEqual(last_event.data[notify.ATTR_TITLE], + 'Test notification') + self.assertEqual(last_event.data[notify.ATTR_MESSAGE], 'Hello') + + def test_send_message_with_data(self): + """Test sending a message with to a notify group.""" + notify_data = {'hello': 'world'} + self.service.send_message('Hello', title='Test notification', + data=notify_data) + self.hass.pool.block_till_done() + last_event = self.events[-1] + self.assertEqual(last_event.data[notify.ATTR_TITLE], + 'Test notification') + self.assertEqual(last_event.data[notify.ATTR_MESSAGE], 'Hello') + self.assertEqual(last_event.data[notify.ATTR_DATA], notify_data) + + def test_entity_data_passes_through(self): + """Test sending a message with data to merge to a notify group.""" + notify_data = {'hello': 'world'} + self.service.send_message('Hello', title='Test notification', + data=notify_data) + self.hass.pool.block_till_done() + data = self.events[-1].data + assert { + 'message': 'Hello', + 'target': 'unnamed device', + 'title': 'Test notification', + 'data': {'hello': 'world', 'test': 'message'} + } == data