From 636de511e46470deeb27b25d4394843bc940218f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Sep 2018 14:20:48 +0200 Subject: [PATCH 1/5] Convert logbook to use attr --- homeassistant/components/logbook.py | 30 ++++++++++------------------- tests/components/test_logbook.py | 4 +++- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index c4fcf53a9..9347198d9 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -8,6 +8,7 @@ from datetime import timedelta from itertools import groupby import logging +import attr import voluptuous as vol from homeassistant.components import sun @@ -134,33 +135,22 @@ class LogbookView(HomeAssistantView): def json_events(): """Fetch events and generate JSON.""" - return self.json(list( - _get_events(hass, self.config, start_day, end_day))) + return self.json([ + attr.asdict(entry) for entry in + _get_events(hass, self.config, start_day, end_day)]) return await hass.async_add_job(json_events) +@attr.s(slots=True, frozen=True) class Entry: """A human readable version of the log.""" - def __init__(self, when=None, name=None, message=None, domain=None, - entity_id=None): - """Initialize the entry.""" - self.when = when - self.name = name - self.message = message - self.domain = domain - self.entity_id = entity_id - - def as_dict(self): - """Convert entry to a dict to be used within JSON.""" - return { - 'when': self.when, - 'name': self.name, - 'message': self.message, - 'domain': self.domain, - 'entity_id': self.entity_id, - } + when = attr.ib(type=str) + name = attr.ib(type=str) + message = attr.ib(type=str) + domain = attr.ib(type=str) + entity_id = attr.ib(type=str, default=None) def humanify(events): diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index cf78fbec3..6c5900688 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -4,6 +4,8 @@ import logging from datetime import timedelta import unittest +import attr + from homeassistant.components import sun import homeassistant.core as ha from homeassistant.const import ( @@ -402,7 +404,7 @@ class TestComponentLogbook(unittest.TestCase): entry = logbook.Entry( dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch' ) - data = entry.as_dict() + data = attr.asdict(entry) self.assertEqual('Alarm', data.get(logbook.ATTR_NAME)) self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE)) self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN)) From 9e7e59f2fe23baa0d414a95d5030c85ad4daa983 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Sep 2018 14:30:31 +0200 Subject: [PATCH 2/5] Add context to events --- homeassistant/components/logbook.py | 38 ++++++++++++++++++++++------- tests/components/test_logbook.py | 11 --------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 9347198d9..fc0d68352 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -150,7 +150,9 @@ class Entry: name = attr.ib(type=str) message = attr.ib(type=str) domain = attr.ib(type=str) + context_id = attr.ib(type=str) entity_id = attr.ib(type=str, default=None) + context_user_id = attr.ib(type=str, default=None) def humanify(events): @@ -215,19 +217,27 @@ def humanify(events): continue yield Entry( - event.time_fired, + when=event.time_fired, name=to_state.name, message=_entry_message_from_state(domain, to_state), domain=domain, - entity_id=to_state.entity_id) + entity_id=to_state.entity_id, + context_id=event.context.id, + context_user_id=event.context.user_id + ) elif event.event_type == EVENT_HOMEASSISTANT_START: if start_stop_events.get(event.time_fired.minute) == 2: continue yield Entry( - event.time_fired, "Home Assistant", "started", - domain=HA_DOMAIN) + when=event.time_fired, + name="Home Assistant", + message="started", + domain=HA_DOMAIN, + context_id=event.context.id, + context_user_id=event.context.user_id + ) elif event.event_type == EVENT_HOMEASSISTANT_STOP: if start_stop_events.get(event.time_fired.minute) == 2: @@ -236,8 +246,13 @@ def humanify(events): action = "stopped" yield Entry( - event.time_fired, "Home Assistant", action, - domain=HA_DOMAIN) + when=event.time_fired, + name="Home Assistant", + message=action, + domain=HA_DOMAIN, + context_id=event.context.id, + context_user_id=event.context.user_id + ) elif event.event_type == EVENT_LOGBOOK_ENTRY: domain = event.data.get(ATTR_DOMAIN) @@ -249,9 +264,14 @@ def humanify(events): pass yield Entry( - event.time_fired, event.data.get(ATTR_NAME), - event.data.get(ATTR_MESSAGE), domain, - entity_id) + when=event.time_fired, + name=event.data.get(ATTR_NAME), + message=event.data.get(ATTR_MESSAGE), + domain=domain, + entity_id=entity_id, + context_id=event.context.id, + context_user_id=event.context.user_id + ) def _get_events(hass, config, start_day, end_day): diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 6c5900688..055097e87 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -399,17 +399,6 @@ class TestComponentLogbook(unittest.TestCase): self.assert_entry(entries[0], pointA, 'bla', domain='switch', entity_id=entity_id) - def test_entry_to_dict(self): - """Test conversion of entry to dict.""" - entry = logbook.Entry( - dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch' - ) - data = attr.asdict(entry) - self.assertEqual('Alarm', data.get(logbook.ATTR_NAME)) - self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE)) - self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN)) - self.assertEqual('test_switch', data.get(logbook.ATTR_ENTITY_ID)) - def test_home_assistant_start_stop_grouped(self): """Test if HA start and stop events are grouped. From ca10d9b726dc146d20c2b65ce1fe433246c9723a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Sep 2018 15:06:32 +0200 Subject: [PATCH 3/5] Enhance logbook --- homeassistant/components/logbook.py | 35 +++++++++++-- tests/components/test_logbook.py | 76 ++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index fc0d68352..aebab83b5 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -18,8 +18,9 @@ from homeassistant.const import ( CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME, STATE_OFF, STATE_ON) -from homeassistant.core import DOMAIN as HA_DOMAIN -from homeassistant.core import State, callback, split_entity_id +from homeassistant.core import ( + DOMAIN as HA_DOMAIN, State, callback, split_entity_id) +from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util @@ -54,7 +55,8 @@ CONFIG_SCHEMA = vol.Schema({ ALL_EVENT_TYPES = [ EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_ALEXA_SMART_HOME ] LOG_MESSAGE_SCHEMA = vol.Schema({ @@ -155,7 +157,7 @@ class Entry: context_user_id = attr.ib(type=str, default=None) -def humanify(events): +def humanify(hass, events): """Generate a converted list of events into Entry objects. Will try to group events if possible: @@ -273,6 +275,29 @@ def humanify(events): context_user_id=event.context.user_id ) + elif event.event_type == EVENT_ALEXA_SMART_HOME: + data = event.data + entity_id = data.get('entity_id') + + if entity_id: + state = hass.states.get(entity_id) + name = state.name if state else entity_id + message = "send command {}/{} for {}".format( + data['namespace'], data['name'], name) + else: + message = "send command {}/{}".format( + data['namespace'], data['name']) + + yield Entry( + when=event.time_fired, + name='Amazon Alexa', + message=message, + domain='alexa', + entity_id=entity_id, + context_id=event.context.id, + context_user_id=event.context.user_id + ) + def _get_events(hass, config, start_day, end_day): """Get events for a period of time.""" @@ -289,7 +314,7 @@ def _get_events(hass, config, start_day, end_day): .filter((States.last_updated == States.last_changed) | (States.state_id.is_(None))) events = execute(query) - return humanify(_exclude_events(events, config)) + return humanify(hass, _exclude_events(events, config)) def _exclude_events(events, config): diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 055097e87..f61c40d34 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -4,8 +4,6 @@ import logging from datetime import timedelta import unittest -import attr - from homeassistant.components import sun import homeassistant.core as ha from homeassistant.const import ( @@ -13,6 +11,7 @@ from homeassistant.const import ( ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF) import homeassistant.util.dt as dt_util from homeassistant.components import logbook, recorder +from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME from homeassistant.setup import setup_component, async_setup_component from tests.common import ( @@ -101,7 +100,7 @@ class TestComponentLogbook(unittest.TestCase): eventB = self.create_state_changed_event(pointB, entity_id, 20) eventC = self.create_state_changed_event(pointC, entity_id, 30) - entries = list(logbook.humanify((eventA, eventB, eventC))) + entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC))) self.assertEqual(2, len(entries)) self.assert_entry( @@ -118,7 +117,7 @@ class TestComponentLogbook(unittest.TestCase): eventA = self.create_state_changed_event( pointA, entity_id, 10, attributes) - entries = list(logbook.humanify((eventA,))) + entries = list(logbook.humanify(self.hass, (eventA,))) self.assertEqual(0, len(entries)) @@ -135,7 +134,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -157,7 +156,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -179,7 +178,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -205,7 +204,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -231,7 +230,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -268,7 +267,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -294,7 +293,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -320,7 +319,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -354,7 +353,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3, eventB1, eventB2), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(3, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -375,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase): {'auto': True}) events = logbook._exclude_events((eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(1, len(entries)) self.assert_entry(entries[0], pointA, 'bla', domain='switch', @@ -393,7 +392,7 @@ class TestComponentLogbook(unittest.TestCase): pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB) events = logbook._exclude_events((eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(1, len(entries)) self.assert_entry(entries[0], pointA, 'bla', domain='switch', @@ -404,7 +403,7 @@ class TestComponentLogbook(unittest.TestCase): Events that are occurring in the same minute. """ - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(EVENT_HOMEASSISTANT_STOP), ha.Event(EVENT_HOMEASSISTANT_START), ))) @@ -419,7 +418,7 @@ class TestComponentLogbook(unittest.TestCase): entity_id = 'switch.bla' pointA = dt_util.utcnow() - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(EVENT_HOMEASSISTANT_START), self.create_state_changed_event(pointA, entity_id, 10) ))) @@ -500,7 +499,7 @@ class TestComponentLogbook(unittest.TestCase): message = 'has a custom entry' entity_id = 'sun.sun' - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { logbook.ATTR_NAME: name, logbook.ATTR_MESSAGE: message, @@ -557,3 +556,44 @@ async def test_logbook_view(hass, aiohttp_client): response = await client.get( '/api/logbook/{}'.format(dt_util.utcnow().isoformat())) assert response.status == 200 + + +async def test_humanify_alexa_event(hass): + """Test humanifying Alexa event.""" + hass.states.async_set('light.kitchen', 'on', { + 'friendly_name': 'Kitchen Light' + }) + + results = list(logbook.humanify(hass, [ + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.Discovery', + 'name': 'Discover', + }), + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.PowerController', + 'name': 'TurnOn', + 'entity_id': 'light.kitchen' + }), + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.PowerController', + 'name': 'TurnOn', + 'entity_id': 'light.non_existing' + }), + + ])) + + event1, event2, event3 = results + + assert event1.name == 'Amazon Alexa' + assert event1.message == 'send command Alexa.Discovery/Discover' + assert event1.entity_id is None + + assert event2.name == 'Amazon Alexa' + assert event2.message == \ + 'send command Alexa.PowerController/TurnOn for Kitchen Light' + assert event2.entity_id == 'light.kitchen' + + assert event3.name == 'Amazon Alexa' + assert event3.message == \ + 'send command Alexa.PowerController/TurnOn for light.non_existing' + assert event3.entity_id == 'light.non_existing' From 3d6198d8dc3bfe2b1afee0f70add3d50358af505 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Sep 2018 17:03:22 +0200 Subject: [PATCH 4/5] Lint --- homeassistant/components/automation/__init__.py | 5 ++--- homeassistant/scripts/benchmark/__init__.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b34a6b7cf..a1f1563f5 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass from homeassistant.const import ( ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID) -from homeassistant.components import logbook from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import extract_domain_configs, script, condition from homeassistant.helpers.entity import ToggleEntity @@ -369,8 +368,8 @@ def _async_get_action(hass, config, name): async def action(entity_id, variables, context): """Execute an action.""" _LOGGER.info('Executing %s', name) - logbook.async_log_entry( - hass, name, 'has been triggered', DOMAIN, entity_id) + hass.components.logbook.async_log_entry( + name, 'has been triggered', DOMAIN, entity_id) await script_obj.async_run(variables, context) return action diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 98de59f2d..f0df58a51 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -186,6 +186,6 @@ def _logbook_filtering(hass, last_changed, last_updated): # pylint: disable=protected-access events = logbook._exclude_events(events, {}) - list(logbook.humanify(events)) + list(logbook.humanify(None, events)) return timer() - start From 43cde6aeaeb13d5711091f87c379121e925a519f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Sep 2018 17:04:25 +0200 Subject: [PATCH 5/5] Fix logbook entry --- homeassistant/components/logbook.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index aebab83b5..abfdcbeb3 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -11,6 +11,7 @@ import logging import attr import voluptuous as vol +from homeassistant.loader import bind_hass from homeassistant.components import sun from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( @@ -67,11 +68,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({ }) +@bind_hass def log_entry(hass, name, message, domain=None, entity_id=None): """Add an entry to the logbook.""" hass.add_job(async_log_entry, hass, name, message, domain, entity_id) +@bind_hass def async_log_entry(hass, name, message, domain=None, entity_id=None): """Add an entry to the logbook.""" data = {