From 9bd508c0bfc00405cb6c4e17f3378f8edce112ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 May 2022 13:17:54 -0400 Subject: [PATCH] Generate json for history and logbook websocket responses in the executor (#71813) --- homeassistant/components/history/__init__.py | 129 ++++++++++++++----- homeassistant/components/logbook/__init__.py | 55 ++++++-- 2 files changed, 138 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index a0d1f2fa76b..17b983e11c1 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -1,12 +1,12 @@ """Provide pre-made queries on top of the recorder component.""" from __future__ import annotations -from collections.abc import Iterable, MutableMapping +from collections.abc import Iterable from datetime import datetime as dt, timedelta from http import HTTPStatus import logging import time -from typing import Any, cast +from typing import Any, Literal, cast from aiohttp import web from sqlalchemy import not_, or_ @@ -24,8 +24,10 @@ from homeassistant.components.recorder.statistics import ( statistics_during_period, ) from homeassistant.components.recorder.util import session_scope +from homeassistant.components.websocket_api import messages +from homeassistant.components.websocket_api.const import JSON_DUMP from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE -from homeassistant.core import HomeAssistant, State +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.deprecation import deprecated_class, deprecated_function from homeassistant.helpers.entityfilter import ( @@ -104,6 +106,23 @@ class LazyState(history_models.LazyState): """A lazy version of core State.""" +def _ws_get_statistics_during_period( + hass: HomeAssistant, + msg_id: int, + start_time: dt, + end_time: dt | None = None, + statistic_ids: list[str] | None = None, + period: Literal["5minute", "day", "hour", "month"] = "hour", +) -> str: + """Fetch statistics and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message( + msg_id, + statistics_during_period(hass, start_time, end_time, statistic_ids, period), + ) + ) + + @websocket_api.websocket_command( { vol.Required("type"): "history/statistics_during_period", @@ -136,15 +155,28 @@ async def ws_get_statistics_during_period( else: end_time = None - statistics = await get_instance(hass).async_add_executor_job( - statistics_during_period, - hass, - start_time, - end_time, - msg.get("statistic_ids"), - msg.get("period"), + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_statistics_during_period, + hass, + msg["id"], + start_time, + end_time, + msg.get("statistic_ids"), + msg.get("period"), + ) + ) + + +def _ws_get_list_statistic_ids( + hass: HomeAssistant, + msg_id: int, + statistic_type: Literal["mean"] | Literal["sum"] | None = None, +) -> str: + """Fetch a list of available statistic_id and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message(msg_id, list_statistic_ids(hass, None, statistic_type)) ) - connection.send_result(msg["id"], statistics) @websocket_api.websocket_command( @@ -158,13 +190,46 @@ async def ws_get_list_statistic_ids( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict ) -> None: """Fetch a list of available statistic_id.""" - statistic_ids = await get_instance(hass).async_add_executor_job( - list_statistic_ids, - hass, - None, - msg.get("statistic_type"), + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_list_statistic_ids, + hass, + msg["id"], + msg.get("statistic_type"), + ) + ) + + +def _ws_get_significant_states( + hass: HomeAssistant, + msg_id: int, + start_time: dt, + end_time: dt | None = None, + entity_ids: list[str] | None = None, + filters: Any | None = None, + include_start_time_state: bool = True, + significant_changes_only: bool = True, + minimal_response: bool = False, + no_attributes: bool = False, +) -> str: + """Fetch history significant_states and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message( + msg_id, + history.get_significant_states( + hass, + start_time, + end_time, + entity_ids, + filters, + include_start_time_state, + significant_changes_only, + minimal_response, + no_attributes, + True, + ), + ) ) - connection.send_result(msg["id"], statistic_ids) @websocket_api.websocket_command( @@ -220,24 +285,22 @@ async def ws_get_history_during_period( significant_changes_only = msg["significant_changes_only"] no_attributes = msg["no_attributes"] minimal_response = msg["minimal_response"] - compressed_state_format = True - history_during_period: MutableMapping[ - str, list[State | dict[str, Any]] - ] = await get_instance(hass).async_add_executor_job( - history.get_significant_states, - hass, - start_time, - end_time, - entity_ids, - hass.data[HISTORY_FILTERS], - include_start_time_state, - significant_changes_only, - minimal_response, - no_attributes, - compressed_state_format, + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_significant_states, + hass, + msg["id"], + start_time, + end_time, + entity_ids, + hass.data[HISTORY_FILTERS], + include_start_time_state, + significant_changes_only, + minimal_response, + no_attributes, + ) ) - connection.send_result(msg["id"], history_during_period) class HistoryPeriodView(HomeAssistantView): diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 592831badf1..4b40e398f6e 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -30,6 +30,8 @@ from homeassistant.components.recorder.models import ( from homeassistant.components.recorder.util import session_scope from homeassistant.components.script import EVENT_SCRIPT_STARTED from homeassistant.components.sensor import ATTR_STATE_CLASS, DOMAIN as SENSOR_DOMAIN +from homeassistant.components.websocket_api import messages +from homeassistant.components.websocket_api.const import JSON_DUMP from homeassistant.const import ( ATTR_DOMAIN, ATTR_ENTITY_ID, @@ -210,6 +212,34 @@ async def _process_logbook_platform( platform.async_describe_events(hass, _async_describe_event) +def _ws_formatted_get_events( + hass: HomeAssistant, + msg_id: int, + start_day: dt, + end_day: dt, + entity_ids: list[str] | None = None, + filters: Filters | None = None, + entities_filter: EntityFilter | Callable[[str], bool] | None = None, + context_id: str | None = None, +) -> str: + """Fetch events and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message( + msg_id, + _get_events( + hass, + start_day, + end_day, + entity_ids, + filters, + entities_filter, + context_id, + True, + ), + ) + ) + + @websocket_api.websocket_command( { vol.Required("type"): "logbook/get_events", @@ -249,20 +279,19 @@ async def ws_get_events( entity_ids = msg.get("entity_ids") context_id = msg.get("context_id") - logbook_events: list[dict[str, Any]] = await get_instance( - hass - ).async_add_executor_job( - _get_events, - hass, - start_time, - end_time, - entity_ids, - hass.data[LOGBOOK_FILTERS], - hass.data[LOGBOOK_ENTITIES_FILTER], - context_id, - True, + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_formatted_get_events, + hass, + msg["id"], + start_time, + end_time, + entity_ids, + hass.data[LOGBOOK_FILTERS], + hass.data[LOGBOOK_ENTITIES_FILTER], + context_id, + ) ) - connection.send_result(msg["id"], logbook_events) class LogbookView(HomeAssistantView):