Deprecate history integration's statistics API (#78056)

This commit is contained in:
Erik Montnemery 2022-09-08 22:03:43 +02:00 committed by GitHub
parent c528a2d2cd
commit 7937bfeedb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 535 additions and 440 deletions

View File

@ -6,22 +6,22 @@ from datetime import datetime as dt, timedelta
from http import HTTPStatus
import logging
import time
from typing import Literal, cast
from typing import cast
from aiohttp import web
import voluptuous as vol
from homeassistant.components import frontend, websocket_api
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.recorder import get_instance, history
from homeassistant.components.recorder import (
get_instance,
history,
websocket_api as recorder_ws,
)
from homeassistant.components.recorder.filters import (
Filters,
sqlalchemy_filter_from_include_exclude_conf,
)
from homeassistant.components.recorder.statistics import (
list_statistic_ids,
statistics_during_period,
)
from homeassistant.components.recorder.util import session_scope
from homeassistant.components.websocket_api import messages
from homeassistant.core import HomeAssistant
@ -68,23 +68,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
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",
@ -99,46 +82,11 @@ async def ws_get_statistics_during_period(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Handle statistics websocket command."""
start_time_str = msg["start_time"]
end_time_str = msg.get("end_time")
if start_time := dt_util.parse_datetime(start_time_str):
start_time = dt_util.as_utc(start_time)
else:
connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time")
return
if end_time_str:
if end_time := dt_util.parse_datetime(end_time_str):
end_time = dt_util.as_utc(end_time)
else:
connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time")
return
else:
end_time = None
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))
_LOGGER.warning(
"WS API 'history/statistics_during_period' is deprecated and will be removed in "
"Home Assistant Core 2022.12. Use 'recorder/statistics_during_period' instead"
)
await recorder_ws.ws_handle_get_statistics_during_period(hass, connection, msg)
@websocket_api.websocket_command(
@ -152,14 +100,11 @@ async def ws_get_list_statistic_ids(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Fetch a list of available statistic_id."""
connection.send_message(
await get_instance(hass).async_add_executor_job(
_ws_get_list_statistic_ids,
hass,
msg["id"],
msg.get("statistic_type"),
)
_LOGGER.warning(
"WS API 'history/list_statistic_ids' is deprecated and will be removed in "
"Home Assistant Core 2022.12. Use 'recorder/list_statistic_ids' instead"
)
await recorder_ws.ws_handle_list_statistic_ids(hass, connection, msg)
def _ws_get_significant_states(

View File

@ -1,13 +1,17 @@
"""The Recorder websocket API."""
from __future__ import annotations
from datetime import datetime as dt
import logging
from typing import Literal
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api import messages
from homeassistant.core import HomeAssistant, callback, valid_entity_id
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.json import JSON_DUMP
from homeassistant.util import dt as dt_util
from .const import MAX_QUEUE_BACKLOG
@ -15,6 +19,7 @@ from .statistics import (
async_add_external_statistics,
async_import_statistics,
list_statistic_ids,
statistics_during_period,
validate_statistics,
)
from .util import async_migration_in_progress, async_migration_is_live, get_instance
@ -25,15 +30,125 @@ _LOGGER: logging.Logger = logging.getLogger(__package__)
@callback
def async_setup(hass: HomeAssistant) -> None:
"""Set up the recorder websocket API."""
websocket_api.async_register_command(hass, ws_validate_statistics)
websocket_api.async_register_command(hass, ws_clear_statistics)
websocket_api.async_register_command(hass, ws_get_statistics_metadata)
websocket_api.async_register_command(hass, ws_update_statistics_metadata)
websocket_api.async_register_command(hass, ws_info)
websocket_api.async_register_command(hass, ws_backup_start)
websocket_api.async_register_command(hass, ws_backup_end)
websocket_api.async_register_command(hass, ws_adjust_sum_statistics)
websocket_api.async_register_command(hass, ws_backup_end)
websocket_api.async_register_command(hass, ws_backup_start)
websocket_api.async_register_command(hass, ws_clear_statistics)
websocket_api.async_register_command(hass, ws_get_statistics_during_period)
websocket_api.async_register_command(hass, ws_get_statistics_metadata)
websocket_api.async_register_command(hass, ws_list_statistic_ids)
websocket_api.async_register_command(hass, ws_import_statistics)
websocket_api.async_register_command(hass, ws_info)
websocket_api.async_register_command(hass, ws_update_statistics_metadata)
websocket_api.async_register_command(hass, ws_validate_statistics)
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),
)
)
async def ws_handle_get_statistics_during_period(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Handle statistics websocket command."""
start_time_str = msg["start_time"]
end_time_str = msg.get("end_time")
if start_time := dt_util.parse_datetime(start_time_str):
start_time = dt_util.as_utc(start_time)
else:
connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time")
return
if end_time_str:
if end_time := dt_util.parse_datetime(end_time_str):
end_time = dt_util.as_utc(end_time)
else:
connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time")
return
else:
end_time = None
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"),
)
)
@websocket_api.websocket_command(
{
vol.Required("type"): "recorder/statistics_during_period",
vol.Required("start_time"): str,
vol.Optional("end_time"): str,
vol.Optional("statistic_ids"): [str],
vol.Required("period"): vol.Any("5minute", "hour", "day", "month"),
}
)
@websocket_api.async_response
async def ws_get_statistics_during_period(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Handle statistics websocket command."""
await ws_handle_get_statistics_during_period(hass, connection, msg)
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))
)
async def ws_handle_list_statistic_ids(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Fetch a list of available statistic_id."""
connection.send_message(
await get_instance(hass).async_add_executor_job(
_ws_get_list_statistic_ids,
hass,
msg["id"],
msg.get("statistic_type"),
)
)
@websocket_api.websocket_command(
{
vol.Required("type"): "recorder/list_statistic_ids",
vol.Optional("statistic_type"): vol.Any("sum", "mean"),
}
)
@websocket_api.async_response
async def ws_list_statistic_ids(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
) -> None:
"""Fetch a list of available statistic_id."""
await ws_handle_list_statistic_ids(hass, connection, msg)
@websocket_api.websocket_command(

View File

@ -5,24 +5,24 @@ from http import HTTPStatus
import json
from unittest.mock import patch, sentinel
from freezegun import freeze_time
import pytest
from pytest import approx
from homeassistant.components import history
from homeassistant.components.recorder.history import get_significant_states
from homeassistant.components.recorder.models import process_timestamp
from homeassistant.components.recorder.websocket_api import (
ws_handle_get_statistics_during_period,
ws_handle_list_statistic_ids,
)
from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE
import homeassistant.core as ha
from homeassistant.helpers.json import JSONEncoder
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
from tests.components.recorder.common import (
async_recorder_block_till_done,
async_wait_recording_done,
do_adhoc_statistics,
wait_recording_done,
)
@ -844,51 +844,13 @@ async def test_entity_ids_limit_via_api_with_skip_initial_state(
assert response_json[1][0]["entity_id"] == "light.cow"
POWER_SENSOR_ATTRIBUTES = {
"device_class": "power",
"state_class": "measurement",
"unit_of_measurement": "kW",
}
PRESSURE_SENSOR_ATTRIBUTES = {
"device_class": "pressure",
"state_class": "measurement",
"unit_of_measurement": "hPa",
}
TEMPERATURE_SENSOR_ATTRIBUTES = {
"device_class": "temperature",
"state_class": "measurement",
"unit_of_measurement": "°C",
}
@pytest.mark.parametrize(
"units, attributes, state, value",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
],
)
async def test_statistics_during_period(
hass, hass_ws_client, recorder_mock, units, attributes, state, value
):
"""Test statistics_during_period."""
async def test_statistics_during_period(hass, hass_ws_client, recorder_mock, caplog):
"""Test history/statistics_during_period forwards to recorder."""
now = dt_util.utcnow()
hass.config.units = units
await async_setup_component(hass, "history", {})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
client = await hass_ws_client()
# Test the WS API works and issues a warning
await client.send_json(
{
"id": 1,
@ -903,322 +865,53 @@ async def test_statistics_during_period(
assert response["success"]
assert response["result"] == {}
await client.send_json(
{
"id": 2,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
assert (
"WS API 'history/statistics_during_period' is deprecated and will be removed in "
"Home Assistant Core 2022.12. Use 'recorder/statistics_during_period' instead"
) in caplog.text
# Test the WS API forwards to recorder
with patch(
"homeassistant.components.history.recorder_ws.ws_handle_get_statistics_during_period",
wraps=ws_handle_get_statistics_during_period,
) as ws_mock:
await client.send_json(
{
"statistic_id": "sensor.test",
"start": now.isoformat(),
"end": (now + timedelta(minutes=5)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
"id": 2,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"end_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "hour",
}
]
}
)
await client.receive_json()
ws_mock.assert_awaited_once()
@pytest.mark.parametrize(
"units, attributes, state, value",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
],
)
async def test_statistics_during_period_in_the_past(
hass, hass_ws_client, recorder_mock, units, attributes, state, value
):
"""Test statistics_during_period in the past."""
hass.config.set_time_zone("UTC")
now = dt_util.utcnow().replace()
hass.config.units = units
async def test_list_statistic_ids(hass, hass_ws_client, recorder_mock, caplog):
"""Test history/list_statistic_ids forwards to recorder."""
await async_setup_component(hass, "history", {})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
past = now - timedelta(days=3)
with freeze_time(past):
hass.states.async_set("sensor.test", state, attributes=attributes)
await async_wait_recording_done(hass)
sensor_state = hass.states.get("sensor.test")
assert sensor_state.last_updated == past
stats_top_of_hour = past.replace(minute=0, second=0, microsecond=0)
stats_start = past.replace(minute=55)
do_adhoc_statistics(hass, start=stats_start)
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"end_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "hour",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
await client.send_json(
{
"id": 2,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
past = now - timedelta(days=3, hours=1)
await client.send_json(
{
"id": 3,
"type": "history/statistics_during_period",
"start_time": past.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
{
"statistic_id": "sensor.test",
"start": stats_start.isoformat(),
"end": (stats_start + timedelta(minutes=5)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
}
]
}
start_of_day = stats_top_of_hour.replace(hour=0, minute=0)
await client.send_json(
{
"id": 4,
"type": "history/statistics_during_period",
"start_time": stats_top_of_hour.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "day",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
{
"statistic_id": "sensor.test",
"start": start_of_day.isoformat(),
"end": (start_of_day + timedelta(days=1)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
}
]
}
await client.send_json(
{
"id": 5,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
async def test_statistics_during_period_bad_start_time(
hass, hass_ws_client, recorder_mock
):
"""Test statistics_during_period."""
await async_setup_component(
hass,
"history",
{"history": {}},
)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "history/statistics_during_period",
"start_time": "cats",
"period": "5minute",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_start_time"
async def test_statistics_during_period_bad_end_time(
hass, hass_ws_client, recorder_mock
):
"""Test statistics_during_period."""
now = dt_util.utcnow()
await async_setup_component(
hass,
"history",
{"history": {}},
)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "history/statistics_during_period",
"start_time": now.isoformat(),
"end_time": "dogs",
"period": "5minute",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_end_time"
@pytest.mark.parametrize(
"units, attributes, display_unit, statistics_unit",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°F", "°C"),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°C", "°C"),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "psi", "Pa"),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "Pa", "Pa"),
],
)
async def test_list_statistic_ids(
hass,
hass_ws_client,
recorder_mock,
units,
attributes,
display_unit,
statistics_unit,
):
"""Test list_statistic_ids."""
now = dt_util.utcnow()
hass.config.units = units
await async_setup_component(hass, "history", {"history": {}})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
client = await hass_ws_client()
# Test the WS API works and issues a warning
await client.send_json({"id": 1, "type": "history/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
hass.states.async_set("sensor.test", 10, attributes=attributes)
await async_wait_recording_done(hass)
assert (
"WS API 'history/list_statistic_ids' is deprecated and will be removed in "
"Home Assistant Core 2022.12. Use 'recorder/list_statistic_ids' instead"
) in caplog.text
await client.send_json({"id": 2, "type": "history/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
do_adhoc_statistics(hass, start=now)
await async_recorder_block_till_done(hass)
# Remove the state, statistics will now be fetched from the database
hass.states.async_remove("sensor.test")
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "history/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
await client.send_json(
{"id": 4, "type": "history/list_statistic_ids", "statistic_type": "dogs"}
)
response = await client.receive_json()
assert not response["success"]
await client.send_json(
{"id": 5, "type": "history/list_statistic_ids", "statistic_type": "mean"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
await client.send_json(
{"id": 6, "type": "history/list_statistic_ids", "statistic_type": "sum"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
with patch(
"homeassistant.components.history.recorder_ws.ws_handle_list_statistic_ids",
wraps=ws_handle_list_statistic_ids,
) as ws_mock:
await client.send_json({"id": 2, "type": "history/list_statistic_ids"})
await client.receive_json()
ws_mock.assert_called_once()
async def test_history_during_period(hass, hass_ws_client, recorder_mock):
@ -1239,7 +932,6 @@ async def test_history_during_period(hass, hass_ws_client, recorder_mock):
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
client = await hass_ws_client()
@ -1358,8 +1050,6 @@ async def test_history_during_period_impossible_conditions(
hass, hass_ws_client, recorder_mock
):
"""Test history_during_period returns when condition cannot be true."""
now = dt_util.utcnow()
await async_setup_component(hass, "history", {})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
@ -1374,7 +1064,6 @@ async def test_history_during_period_impossible_conditions(
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
after = dt_util.utcnow()
@ -1440,7 +1129,6 @@ async def test_history_during_period_significant_domain(
hass.states.async_set("climate.test", "on", attributes={"temperature": "5"})
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
client = await hass_ws_client()
@ -1664,7 +1352,6 @@ async def test_history_during_period_with_use_include_order(
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
client = await hass_ws_client()

View File

@ -4,6 +4,7 @@ from datetime import timedelta
import threading
from unittest.mock import patch
from freezegun import freeze_time
import pytest
from pytest import approx
@ -18,7 +19,7 @@ from homeassistant.components.recorder.statistics import (
from homeassistant.helpers import recorder as recorder_helper
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.util.unit_system import METRIC_SYSTEM
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
from .common import (
async_recorder_block_till_done,
@ -34,6 +35,11 @@ POWER_SENSOR_ATTRIBUTES = {
"state_class": "measurement",
"unit_of_measurement": "kW",
}
PRESSURE_SENSOR_ATTRIBUTES = {
"device_class": "pressure",
"state_class": "measurement",
"unit_of_measurement": "hPa",
}
TEMPERATURE_SENSOR_ATTRIBUTES = {
"device_class": "temperature",
"state_class": "measurement",
@ -51,6 +57,351 @@ GAS_SENSOR_ATTRIBUTES = {
}
@pytest.mark.parametrize(
"units, attributes, state, value",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
],
)
async def test_statistics_during_period(
hass, hass_ws_client, recorder_mock, units, attributes, state, value
):
"""Test statistics_during_period."""
now = dt_util.utcnow()
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"end_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "hour",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
await client.send_json(
{
"id": 2,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
{
"statistic_id": "sensor.test",
"start": now.isoformat(),
"end": (now + timedelta(minutes=5)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
}
]
}
@pytest.mark.parametrize(
"units, attributes, state, value",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
],
)
async def test_statistics_during_period_in_the_past(
hass, hass_ws_client, recorder_mock, units, attributes, state, value
):
"""Test statistics_during_period in the past."""
hass.config.set_time_zone("UTC")
now = dt_util.utcnow().replace()
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
past = now - timedelta(days=3)
with freeze_time(past):
hass.states.async_set("sensor.test", state, attributes=attributes)
await async_wait_recording_done(hass)
sensor_state = hass.states.get("sensor.test")
assert sensor_state.last_updated == past
stats_top_of_hour = past.replace(minute=0, second=0, microsecond=0)
stats_start = past.replace(minute=55)
do_adhoc_statistics(hass, start=stats_start)
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"end_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "hour",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
await client.send_json(
{
"id": 2,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
past = now - timedelta(days=3, hours=1)
await client.send_json(
{
"id": 3,
"type": "recorder/statistics_during_period",
"start_time": past.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
{
"statistic_id": "sensor.test",
"start": stats_start.isoformat(),
"end": (stats_start + timedelta(minutes=5)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
}
]
}
start_of_day = stats_top_of_hour.replace(hour=0, minute=0)
await client.send_json(
{
"id": 4,
"type": "recorder/statistics_during_period",
"start_time": stats_top_of_hour.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "day",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"sensor.test": [
{
"statistic_id": "sensor.test",
"start": start_of_day.isoformat(),
"end": (start_of_day + timedelta(days=1)).isoformat(),
"mean": approx(value),
"min": approx(value),
"max": approx(value),
"last_reset": None,
"state": None,
"sum": None,
}
]
}
await client.send_json(
{
"id": 5,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"statistic_ids": ["sensor.test"],
"period": "5minute",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {}
async def test_statistics_during_period_bad_start_time(
hass, hass_ws_client, recorder_mock
):
"""Test statistics_during_period."""
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "recorder/statistics_during_period",
"start_time": "cats",
"period": "5minute",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_start_time"
async def test_statistics_during_period_bad_end_time(
hass, hass_ws_client, recorder_mock
):
"""Test statistics_during_period."""
now = dt_util.utcnow()
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"end_time": "dogs",
"period": "5minute",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_end_time"
@pytest.mark.parametrize(
"units, attributes, display_unit, statistics_unit",
[
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°F", "°C"),
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°C", "°C"),
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "psi", "Pa"),
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "Pa", "Pa"),
],
)
async def test_list_statistic_ids(
hass,
hass_ws_client,
recorder_mock,
units,
attributes,
display_unit,
statistics_unit,
):
"""Test list_statistic_ids."""
now = dt_util.utcnow()
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
client = await hass_ws_client()
await client.send_json({"id": 1, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
hass.states.async_set("sensor.test", 10, attributes=attributes)
await async_wait_recording_done(hass)
await client.send_json({"id": 2, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
do_adhoc_statistics(hass, start=now)
await async_recorder_block_till_done(hass)
# Remove the state, statistics will now be fetched from the database
hass.states.async_remove("sensor.test")
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
await client.send_json(
{"id": 4, "type": "recorder/list_statistic_ids", "statistic_type": "dogs"}
)
response = await client.receive_json()
assert not response["success"]
await client.send_json(
{"id": 5, "type": "recorder/list_statistic_ids", "statistic_type": "mean"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
{
"statistic_id": "sensor.test",
"has_mean": True,
"has_sum": False,
"name": None,
"source": "recorder",
"display_unit_of_measurement": display_unit,
"statistics_unit_of_measurement": statistics_unit,
}
]
await client.send_json(
{"id": 6, "type": "recorder/list_statistic_ids", "statistic_type": "sum"}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
async def test_validate_statistics(hass, hass_ws_client, recorder_mock):
"""Test validate_statistics can be called."""
id = 1
@ -83,7 +434,6 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
value = 10000
hass.config.units = units
await async_setup_component(hass, "history", {})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test1", state, attributes=attributes)
@ -98,7 +448,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
await client.send_json(
{
"id": 1,
"type": "history/statistics_during_period",
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"period": "5minute",
}
@ -163,7 +513,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
await client.send_json(
{
"id": 3,
"type": "history/statistics_during_period",
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"period": "5minute",
}
@ -187,7 +537,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
await client.send_json(
{
"id": 5,
"type": "history/statistics_during_period",
"type": "recorder/statistics_during_period",
"start_time": now.isoformat(),
"period": "5minute",
}
@ -209,7 +559,6 @@ async def test_update_statistics_metadata(
state = 10
hass.config.units = units
await async_setup_component(hass, "history", {})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
@ -220,7 +569,7 @@ async def test_update_statistics_metadata(
client = await hass_ws_client()
await client.send_json({"id": 1, "type": "history/list_statistic_ids"})
await client.send_json({"id": 1, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
@ -247,7 +596,7 @@ async def test_update_statistics_metadata(
assert response["success"]
await async_recorder_block_till_done(hass)
await client.send_json({"id": 3, "type": "history/list_statistic_ids"})
await client.send_json({"id": 3, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == [
@ -457,7 +806,6 @@ async def test_get_statistics_metadata(
now = dt_util.utcnow()
hass.config.units = units
await async_setup_component(hass, "history", {"history": {}})
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)