From 5bc6ed4cefe884f7f914b9ce0e93f266fae021ba Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Jun 2020 18:14:50 -0700 Subject: [PATCH] Add logbook platforms (#37078) * Add logbook platforms * Fix logbook describe test --- homeassistant/components/alexa/__init__.py | 24 -------- homeassistant/components/alexa/logbook.py | 28 +++++++++ homeassistant/components/alexa/manifest.json | 13 +++- .../components/automation/__init__.py | 13 ---- .../components/automation/logbook.py | 23 ++++++++ .../components/automation/manifest.json | 9 ++- homeassistant/components/homekit/__init__.py | 24 -------- homeassistant/components/homekit/logbook.py | 28 +++++++++ .../components/homekit/manifest.json | 22 +++++-- homeassistant/components/logbook/__init__.py | 25 +++++--- homeassistant/components/script/__init__.py | 13 ---- homeassistant/components/script/logbook.py | 21 +++++++ homeassistant/components/script/manifest.json | 5 +- homeassistant/helpers/integration_platform.py | 16 ++++- tests/components/alexa/test_init.py | 2 + tests/components/automation/test_init.py | 2 + tests/components/homekit/test_init.py | 2 + tests/components/logbook/test_init.py | 59 ++++++++++++------- tests/components/script/test_init.py | 2 + 19 files changed, 215 insertions(+), 116 deletions(-) create mode 100644 homeassistant/components/alexa/logbook.py create mode 100644 homeassistant/components/automation/logbook.py create mode 100644 homeassistant/components/homekit/logbook.py create mode 100644 homeassistant/components/script/logbook.py diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index e8efa8a4752..7522b7e2d58 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -4,7 +4,6 @@ import logging import voluptuous as vol from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_NAME -from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entityfilter from . import flash_briefings, intent, smart_home_http @@ -23,7 +22,6 @@ from .const import ( CONF_TITLE, CONF_UID, DOMAIN, - EVENT_ALEXA_SMART_HOME, ) _LOGGER = logging.getLogger(__name__) @@ -82,28 +80,6 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): """Activate the Alexa component.""" - - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - data = event.data - entity_id = data["request"].get("entity_id") - - if entity_id: - state = hass.states.get(entity_id) - name = state.name if state else entity_id - message = f"send command {data['request']['namespace']}/{data['request']['name']} for {name}" - else: - message = ( - f"send command {data['request']['namespace']}/{data['request']['name']}" - ) - - return {"name": "Amazon Alexa", "message": message, "entity_id": entity_id} - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event - ) - if DOMAIN not in config: return True diff --git a/homeassistant/components/alexa/logbook.py b/homeassistant/components/alexa/logbook.py new file mode 100644 index 00000000000..efc188a7f8b --- /dev/null +++ b/homeassistant/components/alexa/logbook.py @@ -0,0 +1,28 @@ +"""Describe logbook events.""" +from homeassistant.core import callback + +from .const import DOMAIN, EVENT_ALEXA_SMART_HOME + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe a logbook event.""" + data = event.data + entity_id = data["request"].get("entity_id") + + if entity_id: + state = hass.states.get(entity_id) + name = state.name if state else entity_id + message = f"send command {data['request']['namespace']}/{data['request']['name']} for {name}" + else: + message = ( + f"send command {data['request']['namespace']}/{data['request']['name']}" + ) + + return {"name": "Amazon Alexa", "message": message, "entity_id": entity_id} + + async_describe_event(DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event) diff --git a/homeassistant/components/alexa/manifest.json b/homeassistant/components/alexa/manifest.json index 6144ccc6870..1ed91866cdc 100644 --- a/homeassistant/components/alexa/manifest.json +++ b/homeassistant/components/alexa/manifest.json @@ -2,7 +2,14 @@ "domain": "alexa", "name": "Amazon Alexa", "documentation": "https://www.home-assistant.io/integrations/alexa", - "dependencies": ["http"], - "after_dependencies": ["logbook", "camera"], - "codeowners": ["@home-assistant/cloud", "@ochlocracy"] + "dependencies": [ + "http" + ], + "after_dependencies": [ + "camera" + ], + "codeowners": [ + "@home-assistant/cloud", + "@ochlocracy" + ] } diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 8b2c036034b..e5f2f611cdb 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -222,19 +222,6 @@ async def async_setup(hass, config): hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) ) - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - return { - "name": event.data.get(ATTR_NAME), - "message": "has been triggered", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event - ) - return True diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py new file mode 100644 index 00000000000..2e3ad2475fc --- /dev/null +++ b/homeassistant/components/automation/logbook.py @@ -0,0 +1,23 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME +from homeassistant.core import callback + +from . import DOMAIN, EVENT_AUTOMATION_TRIGGERED + + +@callback +def async_describe_events(hass, async_describe_event): # type: ignore + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): # type: ignore + """Describe a logbook event.""" + return { + "name": event.data.get(ATTR_NAME), + "message": "has been triggered", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + async_describe_event( + DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event + ) diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index a93baa0528a..a8dc43844e0 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,12 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "after_dependencies": ["device_automation", "logbook", "webhook"], - "codeowners": ["@home-assistant/core"], + "after_dependencies": [ + "device_automation", + "webhook" + ], + "codeowners": [ + "@home-assistant/core" + ], "quality_scale": "internal" } diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 276a23b7354..c5a921a9dd2 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -21,7 +21,6 @@ from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, - ATTR_SERVICE, CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, @@ -41,12 +40,10 @@ from .accessories import get_accessory from .aidmanager import AccessoryAidStorage from .const import ( AID_STORAGE, - ATTR_DISPLAY_NAME, ATTR_INTERGRATION, ATTR_MANUFACTURER, ATTR_MODEL, ATTR_SOFTWARE_VERSION, - ATTR_VALUE, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CONF_ADVERTISE_IP, @@ -64,7 +61,6 @@ from .const import ( DEFAULT_PORT, DEFAULT_SAFE_MODE, DOMAIN, - EVENT_HOMEKIT_CHANGED, HOMEKIT, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, @@ -325,26 +321,6 @@ def _async_register_events_and_services(hass: HomeAssistant): schema=RESET_ACCESSORY_SERVICE_SCHEMA, ) - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - data = event.data - entity_id = data.get(ATTR_ENTITY_ID) - value = data.get(ATTR_VALUE) - - value_msg = f" to {value}" if value else "" - message = f"send command {data[ATTR_SERVICE]}{value_msg} for {data[ATTR_DISPLAY_NAME]}" - - return { - "name": "HomeKit", - "message": message, - "entity_id": entity_id, - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event - ) - async def async_handle_homekit_service_start(service): """Handle start HomeKit service call.""" for entry_id in hass.data[DOMAIN]: diff --git a/homeassistant/components/homekit/logbook.py b/homeassistant/components/homekit/logbook.py new file mode 100644 index 00000000000..0ea5a5d542a --- /dev/null +++ b/homeassistant/components/homekit/logbook.py @@ -0,0 +1,28 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE +from homeassistant.core import callback + +from .const import ATTR_DISPLAY_NAME, ATTR_VALUE, DOMAIN, EVENT_HOMEKIT_CHANGED + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe a logbook event.""" + data = event.data + entity_id = data.get(ATTR_ENTITY_ID) + value = data.get(ATTR_VALUE) + + value_msg = f" to {value}" if value else "" + message = f"send command {data[ATTR_SERVICE]}{value_msg} for {data[ATTR_DISPLAY_NAME]}" + + return { + "name": "HomeKit", + "message": message, + "entity_id": entity_id, + } + + async_describe_event(DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 985fcc1e799..8a5fc90ae07 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -2,9 +2,23 @@ "domain": "homekit", "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", - "requirements": ["HAP-python==2.9.1","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1","PyTurboJPEG==1.4.0"], - "dependencies": ["http", "camera", "ffmpeg"], - "after_dependencies": ["logbook", "zeroconf"], - "codeowners": ["@bdraco"], + "requirements": [ + "HAP-python==2.9.1", + "fnvhash==0.1.0", + "PyQRCode==1.2.1", + "base36==0.1.1", + "PyTurboJPEG==1.4.0" + ], + "dependencies": [ + "http", + "camera", + "ffmpeg" + ], + "after_dependencies": [ + "zeroconf" + ], + "codeowners": [ + "@bdraco" + ], "config_flow": true } diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 13253300cf3..28d6c7fcd48 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -47,6 +47,9 @@ from homeassistant.helpers.entityfilter import ( convert_include_exclude_filter, generate_filter, ) +from homeassistant.helpers.integration_platform import ( + async_process_integration_platforms, +) from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util @@ -102,15 +105,9 @@ def async_log_entry(hass, name, message, domain=None, entity_id=None): hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data) -@bind_hass -def async_describe_event(hass, domain, event_name, describe_callback): - """Teach logbook how to describe a new event.""" - hass.data.setdefault(DOMAIN, {})[event_name] = (domain, describe_callback) - - async def async_setup(hass, config): """Logbook setup.""" - hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN] = {} @callback def log_message(service): @@ -131,9 +128,23 @@ async def async_setup(hass, config): ) hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA) + + await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform) + return True +async def _process_logbook_platform(hass, domain, platform): + """Process a logbook platform.""" + + @callback + def _async_describe_event(domain, event_name, describe_callback): + """Teach logbook how to describe a new event.""" + hass.data[DOMAIN][event_name] = (domain, describe_callback) + + platform.async_describe_events(hass, _async_describe_event) + + class LogbookView(HomeAssistantView): """Handle logbook view requests.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index b9043ff2f09..e80dcfa8027 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -188,19 +188,6 @@ async def async_setup(hass, config): DOMAIN, SERVICE_TOGGLE, toggle_service, schema=SCRIPT_TURN_ONOFF_SCHEMA ) - @callback - def async_describe_logbook_event(event): - """Describe the logbook event.""" - return { - "name": event.data.get(ATTR_NAME), - "message": "started", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event - ) - return True diff --git a/homeassistant/components/script/logbook.py b/homeassistant/components/script/logbook.py new file mode 100644 index 00000000000..72ff0d15fc7 --- /dev/null +++ b/homeassistant/components/script/logbook.py @@ -0,0 +1,21 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME +from homeassistant.core import callback + +from . import DOMAIN, EVENT_SCRIPT_STARTED + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe the logbook event.""" + return { + "name": event.data.get(ATTR_NAME), + "message": "started", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + async_describe_event(DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event) diff --git a/homeassistant/components/script/manifest.json b/homeassistant/components/script/manifest.json index 9348469d258..b9d333ce553 100644 --- a/homeassistant/components/script/manifest.json +++ b/homeassistant/components/script/manifest.json @@ -2,7 +2,8 @@ "domain": "script", "name": "Scripts", "documentation": "https://www.home-assistant.io/integrations/script", - "after_dependencies": ["logbook"], - "codeowners": ["@home-assistant/core"], + "codeowners": [ + "@home-assistant/core" + ], "quality_scale": "internal" } diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 01567c72c7b..93f3f3f7427 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -4,7 +4,7 @@ import logging from typing import Any, Awaitable, Callable from homeassistant.core import Event, HomeAssistant -from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass +from homeassistant.loader import async_get_integration, bind_hass from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED _LOGGER = logging.getLogger(__name__) @@ -21,10 +21,20 @@ async def async_process_integration_platforms( async def _process(component_name: str) -> None: """Process the intents of a component.""" + if "." in component_name: + return + + integration = await async_get_integration(hass, component_name) + try: - integration = await async_get_integration(hass, component_name) platform = integration.get_platform(platform_name) - except (IntegrationNotFound, ImportError): + except ImportError as err: + if f"{component_name}.{platform_name}" not in str(err): + _LOGGER.exception( + "Unexpected error importing %s/%s.py", + component_name, + platform_name, + ) return try: diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py index f5071cf3f01..605ca96f190 100644 --- a/tests/components/alexa/test_init.py +++ b/tests/components/alexa/test_init.py @@ -8,7 +8,9 @@ from tests.components.logbook.test_init import MockLazyEventPartialState async def test_humanify_alexa_event(hass): """Test humanifying Alexa event.""" + hass.config.components.add("recorder") await async_setup_component(hass, "alexa", {}) + await async_setup_component(hass, "logbook", {}) hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"}) entity_attr_cache = logbook.EntityAttributeCache(hass) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index c41eb80d6f2..9af8a6591d9 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1039,7 +1039,9 @@ async def test_extraction_functions(hass): async def test_logbook_humanify_automation_triggered_event(hass): """Test humanifying Automation Trigger event.""" + hass.config.components.add("recorder") await async_setup_component(hass, automation.DOMAIN, {}) + await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list( diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 4db72ffb374..1fad563445b 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -15,8 +15,10 @@ from tests.components.logbook.test_init import MockLazyEventPartialState async def test_humanify_homekit_changed_event(hass, hk_driver): """Test humanifying HomeKit changed event.""" + hass.config.components.add("recorder") with patch("homeassistant.components.homekit.HomeKit"): assert await async_setup_component(hass, "homekit", {"homekit": {}}) + assert await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list( diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 58c918477f8..03ef09b438d 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -36,8 +36,8 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util -from tests.async_mock import patch -from tests.common import get_test_home_assistant, init_recorder_component +from tests.async_mock import Mock, patch +from tests.common import get_test_home_assistant, init_recorder_component, mock_platform from tests.components.recorder.common import trigger_db_commit _LOGGER = logging.getLogger(__name__) @@ -1563,6 +1563,22 @@ async def test_logbook_view_period_entity(hass, hass_client): async def test_logbook_describe_event(hass, hass_client): """Test teaching logbook about a new event.""" await hass.async_add_executor_job(init_recorder_component, hass) + + def _describe(event): + """Describe an event.""" + return {"name": "Test Name", "message": "tested a message"} + + hass.config.components.add("fake_integration") + mock_platform( + hass, + "fake_integration.logbook", + Mock( + async_describe_events=lambda hass, async_describe_event: async_describe_event( + "test_domain", "some_event", _describe + ) + ), + ) + assert await async_setup_component(hass, "logbook", {}) with patch( "homeassistant.util.dt.utcnow", @@ -1574,12 +1590,6 @@ async def test_logbook_describe_event(hass, hass_client): hass.data[recorder.DATA_INSTANCE].block_till_done ) - def _describe(event): - """Describe an event.""" - return {"name": "Test Name", "message": "tested a message"} - - hass.components.logbook.async_describe_event("test_domain", "some_event", _describe) - client = await hass_client() response = await client.get("/api/logbook") results = await response.json() @@ -1597,6 +1607,26 @@ async def test_exclude_described_event(hass, hass_client): entity_id2 = "automation.included_rule" entity_id3 = "sensor.excluded_domain" + def _describe(event): + """Describe an event.""" + return { + "name": "Test Name", + "message": "tested a message", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + def async_describe_events(hass, async_describe_event): + """Mock to describe events.""" + async_describe_event("automation", "some_automation_event", _describe) + async_describe_event("sensor", "some_event", _describe) + + hass.config.components.add("fake_integration") + mock_platform( + hass, + "fake_integration.logbook", + Mock(async_describe_events=async_describe_events), + ) + await hass.async_add_executor_job(init_recorder_component, hass) assert await async_setup_component( hass, @@ -1631,19 +1661,6 @@ async def test_exclude_described_event(hass, hass_client): hass.data[recorder.DATA_INSTANCE].block_till_done ) - def _describe(event): - """Describe an event.""" - return { - "name": "Test Name", - "message": "tested a message", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - "automation", "some_automation_event", _describe - ) - hass.components.logbook.async_describe_event("sensor", "some_event", _describe) - client = await hass_client() response = await client.get("/api/logbook") results = await response.json() diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 9bcf0dc1be8..bb7340a08da 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -472,7 +472,9 @@ async def test_config(hass): async def test_logbook_humanify_script_started_event(hass): """Test humanifying script started event.""" + hass.config.components.add("recorder") await async_setup_component(hass, DOMAIN, {}) + await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list(