From 2ac44f60839cb572627f1e7b47a5c8f87adefa2f Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:10:18 -0700 Subject: [PATCH] Make recorder.purge_entities require at least one entity filter value (#110066) Co-authored-by: J. Nick Koston --- homeassistant/components/recorder/services.py | 30 ++++++++++++++----- .../components/recorder/services.yaml | 9 +++--- .../components/recorder/strings.json | 4 +++ tests/components/recorder/test_purge.py | 11 +++---- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/recorder/services.py b/homeassistant/components/recorder/services.py index b4d719a9481..2be02fe8091 100644 --- a/homeassistant/components/recorder/services.py +++ b/homeassistant/components/recorder/services.py @@ -7,6 +7,7 @@ from typing import cast import voluptuous as vol +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import generate_filter @@ -36,15 +37,28 @@ SERVICE_PURGE_SCHEMA = vol.Schema( ATTR_DOMAINS = "domains" ATTR_ENTITY_GLOBS = "entity_globs" -SERVICE_PURGE_ENTITIES_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_ENTITY_GLOBS, default=[]): vol.All( - cv.ensure_list, [cv.string] +SERVICE_PURGE_ENTITIES_SCHEMA = vol.All( + vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID, default=[]): cv.entity_ids, + vol.Optional(ATTR_DOMAINS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(ATTR_ENTITY_GLOBS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(ATTR_KEEP_DAYS, default=0): cv.positive_int, + } + ), + vol.Any( + vol.Schema({vol.Required(ATTR_ENTITY_ID): vol.IsTrue()}, extra=vol.ALLOW_EXTRA), + vol.Schema({vol.Required(ATTR_DOMAINS): vol.IsTrue()}, extra=vol.ALLOW_EXTRA), + vol.Schema( + {vol.Required(ATTR_ENTITY_GLOBS): vol.IsTrue()}, extra=vol.ALLOW_EXTRA ), - vol.Optional(ATTR_KEEP_DAYS, default=0): cv.positive_int, - } -).extend(cv.ENTITY_SERVICE_FIELDS) + msg="At least one of entity_id, domains, or entity_globs must have a value", + ), +) SERVICE_ENABLE_SCHEMA = vol.Schema({}) SERVICE_DISABLE_SCHEMA = vol.Schema({}) diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index b74dcc2a494..7d7b926548c 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -20,20 +20,21 @@ purge: boolean: purge_entities: - target: - entity: {} fields: + entity_id: + required: false + selector: + entity: + multiple: true domains: example: "sun" required: false - default: [] selector: object: entity_globs: example: "domain*.object_id*" required: false - default: [] selector: object: diff --git a/homeassistant/components/recorder/strings.json b/homeassistant/components/recorder/strings.json index 74b248354d7..bf5d95ae1fc 100644 --- a/homeassistant/components/recorder/strings.json +++ b/homeassistant/components/recorder/strings.json @@ -41,6 +41,10 @@ "name": "Purge entities", "description": "Starts a purge task to remove the data related to specific entities from your database.", "fields": { + "entity_id": { + "name": "Entities to remove", + "description": "List of entities for which the data is to be removed from the recorder database." + }, "domains": { "name": "Domains to remove", "description": "List of domains for which the data needs to be removed from the recorder database." diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index b2da3f1d62f..e80bc7ca7d1 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -9,6 +9,7 @@ from freezegun import freeze_time import pytest from sqlalchemy.exc import DatabaseError, OperationalError from sqlalchemy.orm.session import Session +from voluptuous.error import MultipleInvalid from homeassistant.components import recorder from homeassistant.components.recorder.const import SupportedDialect @@ -1446,20 +1447,20 @@ async def test_purge_entities( _add_purge_records(hass) - # Confirm calling service without arguments matches all records (default filter behavior) + # Confirm calling service without arguments is invalid with session_scope(hass=hass) as session: states = session.query(States) assert states.count() == 190 - await _purge_entities(hass, [], [], []) + with pytest.raises(MultipleInvalid): + await _purge_entities(hass, [], [], []) with session_scope(hass=hass, read_only=True) as session: states = session.query(States) - assert states.count() == 0 + assert states.count() == 190 - # The states_meta table should be empty states_meta_remain = session.query(StatesMeta) - assert states_meta_remain.count() == 0 + assert states_meta_remain.count() == 4 async def _add_test_states(hass: HomeAssistant, wait_recording_done: bool = True):