From bbcefd6fecc6c8a75a03386ed810ca67d9733ead Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 29 Mar 2022 23:03:08 -1000 Subject: [PATCH] Exclude sun attributes from being recorded in the database (#68887) --- homeassistant/components/sun/__init__.py | 49 ++++++++++++++------ homeassistant/components/sun/recorder.py | 32 +++++++++++++ tests/components/sun/test_recorder.py | 59 ++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/sun/recorder.py create mode 100644 tests/components/sun/test_recorder.py diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index b569e9df7bd..c619607d5b2 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -5,11 +5,12 @@ import logging from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_ELEVATION, + EVENT_COMPONENT_LOADED, EVENT_CORE_CONFIG_UPDATE, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import event from homeassistant.helpers.entity import Entity from homeassistant.helpers.sun import ( @@ -17,6 +18,7 @@ from homeassistant.helpers.sun import ( get_location_astral_event_next, ) from homeassistant.helpers.typing import ConfigType +from homeassistant.setup import ATTR_COMPONENT from homeassistant.util import dt as dt_util from .const import DOMAIN @@ -124,26 +126,43 @@ class Sun(Entity): self._config_listener = None self._update_events_listener = None self._update_sun_position_listener = None - - @callback - def update_location(_event): - location, elevation = get_astral_location(self.hass) - if location == self.location: - return - self.location = location - self.elevation = elevation - if self._update_events_listener: - self._update_events_listener() - self.update_events() - - update_location(None) + self._loaded_listener = None self._config_listener = self.hass.bus.async_listen( - EVENT_CORE_CONFIG_UPDATE, update_location + EVENT_CORE_CONFIG_UPDATE, self.update_location ) + self._loaded_listener = self.hass.bus.async_listen( + EVENT_COMPONENT_LOADED, self.loading_complete + ) + + @callback + def loading_complete(self, event_: Event) -> None: + """Update location when loading is complete.""" + if event_.data[ATTR_COMPONENT] == DOMAIN: + self.update_location() + self._remove_loaded_listener() + + @callback + def update_location(self, *_): + """Update location.""" + location, elevation = get_astral_location(self.hass) + if location == self.location: + return + self.location = location + self.elevation = elevation + if self._update_events_listener: + self._update_events_listener() + self.update_events() + + @callback + def _remove_loaded_listener(self): + """Remove the loaded listener.""" + if self._loaded_listener: + self._loaded_listener() @callback def remove_listeners(self): """Remove listeners.""" + self._remove_loaded_listener() if self._config_listener: self._config_listener() if self._update_events_listener: diff --git a/homeassistant/components/sun/recorder.py b/homeassistant/components/sun/recorder.py new file mode 100644 index 00000000000..710d7ff4559 --- /dev/null +++ b/homeassistant/components/sun/recorder.py @@ -0,0 +1,32 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ( + STATE_ATTR_AZIMUTH, + STATE_ATTR_ELEVATION, + STATE_ATTR_NEXT_DAWN, + STATE_ATTR_NEXT_DUSK, + STATE_ATTR_NEXT_MIDNIGHT, + STATE_ATTR_NEXT_NOON, + STATE_ATTR_NEXT_RISING, + STATE_ATTR_NEXT_SETTING, + STATE_ATTR_RISING, +) + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude sun attributes from being recorded in the database.""" + return { + STATE_ATTR_AZIMUTH, + STATE_ATTR_ELEVATION, + STATE_ATTR_RISING, + STATE_ATTR_NEXT_DAWN, + STATE_ATTR_NEXT_DUSK, + STATE_ATTR_NEXT_MIDNIGHT, + STATE_ATTR_NEXT_NOON, + STATE_ATTR_NEXT_RISING, + STATE_ATTR_NEXT_SETTING, + } diff --git a/tests/components/sun/test_recorder.py b/tests/components/sun/test_recorder.py new file mode 100644 index 00000000000..08e68fc7555 --- /dev/null +++ b/tests/components/sun/test_recorder.py @@ -0,0 +1,59 @@ +"""The tests for sun recorder.""" +from __future__ import annotations + +from datetime import timedelta + +from homeassistant.components.recorder.models import StateAttributes, States +from homeassistant.components.recorder.util import session_scope +from homeassistant.components.sun import ( + DOMAIN, + STATE_ATTR_AZIMUTH, + STATE_ATTR_ELEVATION, + STATE_ATTR_NEXT_DAWN, + STATE_ATTR_NEXT_DUSK, + STATE_ATTR_NEXT_MIDNIGHT, + STATE_ATTR_NEXT_NOON, + STATE_ATTR_NEXT_RISING, + STATE_ATTR_NEXT_SETTING, + STATE_ATTR_RISING, +) +from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.core import State +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util + +from tests.common import async_fire_time_changed, async_init_recorder_component +from tests.components.recorder.common import async_wait_recording_done_without_instance + + +async def test_exclude_attributes(hass): + """Test sun attributes to be excluded.""" + await async_init_recorder_component(hass) + await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + await async_wait_recording_done_without_instance(hass) + + def _fetch_sun_states() -> list[State]: + with session_scope(hass=hass) as session: + native_states = [] + for db_state, db_state_attributes in session.query(States, StateAttributes): + state = db_state.to_native() + state.attributes = db_state_attributes.to_native() + native_states.append(state) + return native_states + + states: list[State] = await hass.async_add_executor_job(_fetch_sun_states) + assert len(states) > 1 + for state in states: + assert STATE_ATTR_AZIMUTH not in state.attributes + assert STATE_ATTR_ELEVATION not in state.attributes + assert STATE_ATTR_NEXT_DAWN not in state.attributes + assert STATE_ATTR_NEXT_DUSK not in state.attributes + assert STATE_ATTR_NEXT_MIDNIGHT not in state.attributes + assert STATE_ATTR_NEXT_NOON not in state.attributes + assert STATE_ATTR_NEXT_RISING not in state.attributes + assert STATE_ATTR_NEXT_SETTING not in state.attributes + assert STATE_ATTR_RISING not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes