mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 06:47:09 +00:00
Remove support for excluding attributes in recorder platforms (#100679)
This commit is contained in:
parent
1b1901cb6d
commit
0dc21504f5
@ -27,9 +27,7 @@ from .const import ( # noqa: F401
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_RECORDER_5MIN_STATISTICS_GENERATED,
|
EVENT_RECORDER_5MIN_STATISTICS_GENERATED,
|
||||||
EVENT_RECORDER_HOURLY_STATISTICS_GENERATED,
|
EVENT_RECORDER_HOURLY_STATISTICS_GENERATED,
|
||||||
EXCLUDE_ATTRIBUTES,
|
|
||||||
INTEGRATION_PLATFORM_COMPILE_STATISTICS,
|
INTEGRATION_PLATFORM_COMPILE_STATISTICS,
|
||||||
INTEGRATION_PLATFORM_EXCLUDE_ATTRIBUTES,
|
|
||||||
INTEGRATION_PLATFORMS_LOAD_IN_RECORDER_THREAD,
|
INTEGRATION_PLATFORMS_LOAD_IN_RECORDER_THREAD,
|
||||||
SQLITE_URL_PREFIX,
|
SQLITE_URL_PREFIX,
|
||||||
SupportedDialect,
|
SupportedDialect,
|
||||||
@ -132,8 +130,6 @@ def is_entity_recorded(hass: HomeAssistant, entity_id: str) -> bool:
|
|||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the recorder."""
|
"""Set up the recorder."""
|
||||||
exclude_attributes_by_domain: dict[str, set[str]] = {}
|
|
||||||
hass.data[EXCLUDE_ATTRIBUTES] = exclude_attributes_by_domain
|
|
||||||
conf = config[DOMAIN]
|
conf = config[DOMAIN]
|
||||||
entity_filter = convert_include_exclude_filter(conf).get_filter()
|
entity_filter = convert_include_exclude_filter(conf).get_filter()
|
||||||
auto_purge = conf[CONF_AUTO_PURGE]
|
auto_purge = conf[CONF_AUTO_PURGE]
|
||||||
@ -161,7 +157,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
db_retry_wait=db_retry_wait,
|
db_retry_wait=db_retry_wait,
|
||||||
entity_filter=entity_filter,
|
entity_filter=entity_filter,
|
||||||
exclude_event_types=exclude_event_types,
|
exclude_event_types=exclude_event_types,
|
||||||
exclude_attributes_by_domain=exclude_attributes_by_domain,
|
|
||||||
)
|
)
|
||||||
instance.async_initialize()
|
instance.async_initialize()
|
||||||
instance.async_register()
|
instance.async_register()
|
||||||
@ -170,17 +165,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
websocket_api.async_setup(hass)
|
websocket_api.async_setup(hass)
|
||||||
entity_registry.async_setup(hass)
|
entity_registry.async_setup(hass)
|
||||||
|
|
||||||
await _async_setup_integration_platform(
|
await _async_setup_integration_platform(hass, instance)
|
||||||
hass, instance, exclude_attributes_by_domain
|
|
||||||
)
|
|
||||||
|
|
||||||
return await instance.async_db_ready
|
return await instance.async_db_ready
|
||||||
|
|
||||||
|
|
||||||
async def _async_setup_integration_platform(
|
async def _async_setup_integration_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant, instance: Recorder
|
||||||
instance: Recorder,
|
|
||||||
exclude_attributes_by_domain: dict[str, set[str]],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a recorder integration platform."""
|
"""Set up a recorder integration platform."""
|
||||||
|
|
||||||
@ -188,15 +179,6 @@ async def _async_setup_integration_platform(
|
|||||||
hass: HomeAssistant, domain: str, platform: Any
|
hass: HomeAssistant, domain: str, platform: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Process a recorder platform."""
|
"""Process a recorder platform."""
|
||||||
# We need to add this before as soon as the component is loaded
|
|
||||||
# to ensure by the time the state is recorded that the excluded
|
|
||||||
# attributes are known. This is safe to modify in the event loop
|
|
||||||
# since exclude_attributes_by_domain is never iterated over.
|
|
||||||
if exclude_attributes := getattr(
|
|
||||||
platform, INTEGRATION_PLATFORM_EXCLUDE_ATTRIBUTES, None
|
|
||||||
):
|
|
||||||
exclude_attributes_by_domain[domain] = exclude_attributes(hass)
|
|
||||||
|
|
||||||
# If the platform has a compile_statistics method, we need to
|
# If the platform has a compile_statistics method, we need to
|
||||||
# add it to the recorder queue to be processed.
|
# add it to the recorder queue to be processed.
|
||||||
if any(
|
if any(
|
||||||
|
@ -40,10 +40,6 @@ ATTR_APPLY_FILTER = "apply_filter"
|
|||||||
|
|
||||||
KEEPALIVE_TIME = 30
|
KEEPALIVE_TIME = 30
|
||||||
|
|
||||||
|
|
||||||
EXCLUDE_ATTRIBUTES = f"{DOMAIN}_exclude_attributes_by_domain"
|
|
||||||
|
|
||||||
|
|
||||||
STATISTICS_ROWS_SCHEMA_VERSION = 23
|
STATISTICS_ROWS_SCHEMA_VERSION = 23
|
||||||
CONTEXT_ID_AS_BINARY_SCHEMA_VERSION = 36
|
CONTEXT_ID_AS_BINARY_SCHEMA_VERSION = 36
|
||||||
EVENT_TYPE_IDS_SCHEMA_VERSION = 37
|
EVENT_TYPE_IDS_SCHEMA_VERSION = 37
|
||||||
@ -51,9 +47,6 @@ STATES_META_SCHEMA_VERSION = 38
|
|||||||
|
|
||||||
LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION = 28
|
LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION = 28
|
||||||
|
|
||||||
|
|
||||||
INTEGRATION_PLATFORM_EXCLUDE_ATTRIBUTES = "exclude_attributes"
|
|
||||||
|
|
||||||
INTEGRATION_PLATFORM_COMPILE_STATISTICS = "compile_statistics"
|
INTEGRATION_PLATFORM_COMPILE_STATISTICS = "compile_statistics"
|
||||||
INTEGRATION_PLATFORM_VALIDATE_STATISTICS = "validate_statistics"
|
INTEGRATION_PLATFORM_VALIDATE_STATISTICS = "validate_statistics"
|
||||||
INTEGRATION_PLATFORM_LIST_STATISTIC_IDS = "list_statistic_ids"
|
INTEGRATION_PLATFORM_LIST_STATISTIC_IDS = "list_statistic_ids"
|
||||||
|
@ -177,7 +177,6 @@ class Recorder(threading.Thread):
|
|||||||
db_retry_wait: int,
|
db_retry_wait: int,
|
||||||
entity_filter: Callable[[str], bool],
|
entity_filter: Callable[[str], bool],
|
||||||
exclude_event_types: set[str],
|
exclude_event_types: set[str],
|
||||||
exclude_attributes_by_domain: dict[str, set[str]],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the recorder."""
|
"""Initialize the recorder."""
|
||||||
threading.Thread.__init__(self, name="Recorder")
|
threading.Thread.__init__(self, name="Recorder")
|
||||||
@ -221,9 +220,7 @@ class Recorder(threading.Thread):
|
|||||||
self.event_data_manager = EventDataManager(self)
|
self.event_data_manager = EventDataManager(self)
|
||||||
self.event_type_manager = EventTypeManager(self)
|
self.event_type_manager = EventTypeManager(self)
|
||||||
self.states_meta_manager = StatesMetaManager(self)
|
self.states_meta_manager = StatesMetaManager(self)
|
||||||
self.state_attributes_manager = StateAttributesManager(
|
self.state_attributes_manager = StateAttributesManager(self)
|
||||||
self, exclude_attributes_by_domain
|
|
||||||
)
|
|
||||||
self.statistics_meta_manager = StatisticsMetaManager(self)
|
self.statistics_meta_manager = StatisticsMetaManager(self)
|
||||||
|
|
||||||
self.event_session: Session | None = None
|
self.event_session: Session | None = None
|
||||||
|
@ -39,7 +39,7 @@ from homeassistant.const import (
|
|||||||
MAX_LENGTH_STATE_ENTITY_ID,
|
MAX_LENGTH_STATE_ENTITY_ID,
|
||||||
MAX_LENGTH_STATE_STATE,
|
MAX_LENGTH_STATE_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
from homeassistant.core import Context, Event, EventOrigin, State
|
||||||
from homeassistant.helpers.entity import EntityInfo
|
from homeassistant.helpers.entity import EntityInfo
|
||||||
from homeassistant.helpers.json import JSON_DUMP, json_bytes, json_bytes_strip_null
|
from homeassistant.helpers.json import JSON_DUMP, json_bytes, json_bytes_strip_null
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -560,7 +560,6 @@ class StateAttributes(Base):
|
|||||||
def shared_attrs_bytes_from_event(
|
def shared_attrs_bytes_from_event(
|
||||||
event: Event,
|
event: Event,
|
||||||
entity_sources: dict[str, EntityInfo],
|
entity_sources: dict[str, EntityInfo],
|
||||||
exclude_attrs_by_domain: dict[str, set[str]],
|
|
||||||
dialect: SupportedDialect | None,
|
dialect: SupportedDialect | None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
"""Create shared_attrs from a state_changed event."""
|
"""Create shared_attrs from a state_changed event."""
|
||||||
@ -568,14 +567,7 @@ class StateAttributes(Base):
|
|||||||
# None state means the state was removed from the state machine
|
# None state means the state was removed from the state machine
|
||||||
if state is None:
|
if state is None:
|
||||||
return b"{}"
|
return b"{}"
|
||||||
domain = split_entity_id(state.entity_id)[0]
|
|
||||||
exclude_attrs = set(ALL_DOMAIN_EXCLUDE_ATTRS)
|
exclude_attrs = set(ALL_DOMAIN_EXCLUDE_ATTRS)
|
||||||
if base_platform_attrs := exclude_attrs_by_domain.get(domain):
|
|
||||||
exclude_attrs |= base_platform_attrs
|
|
||||||
if (entity_info := entity_sources.get(state.entity_id)) and (
|
|
||||||
integration_attrs := exclude_attrs_by_domain.get(entity_info["domain"])
|
|
||||||
):
|
|
||||||
exclude_attrs |= integration_attrs
|
|
||||||
if state_info := state.state_info:
|
if state_info := state.state_info:
|
||||||
exclude_attrs |= state_info["unrecorded_attributes"]
|
exclude_attrs |= state_info["unrecorded_attributes"]
|
||||||
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
||||||
|
@ -34,13 +34,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class StateAttributesManager(BaseLRUTableManager[StateAttributes]):
|
class StateAttributesManager(BaseLRUTableManager[StateAttributes]):
|
||||||
"""Manage the StateAttributes table."""
|
"""Manage the StateAttributes table."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, recorder: Recorder) -> None:
|
||||||
self, recorder: Recorder, exclude_attributes_by_domain: dict[str, set[str]]
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the event type manager."""
|
"""Initialize the event type manager."""
|
||||||
super().__init__(recorder, CACHE_SIZE)
|
super().__init__(recorder, CACHE_SIZE)
|
||||||
self.active = True # always active
|
self.active = True # always active
|
||||||
self._exclude_attributes_by_domain = exclude_attributes_by_domain
|
|
||||||
self._entity_sources = entity_sources(recorder.hass)
|
self._entity_sources = entity_sources(recorder.hass)
|
||||||
|
|
||||||
def serialize_from_event(self, event: Event) -> bytes | None:
|
def serialize_from_event(self, event: Event) -> bytes | None:
|
||||||
@ -49,7 +46,6 @@ class StateAttributesManager(BaseLRUTableManager[StateAttributes]):
|
|||||||
return StateAttributes.shared_attrs_bytes_from_event(
|
return StateAttributes.shared_attrs_bytes_from_event(
|
||||||
event,
|
event,
|
||||||
self._entity_sources,
|
self._entity_sources,
|
||||||
self._exclude_attributes_by_domain,
|
|
||||||
self.recorder.dialect_name,
|
self.recorder.dialect_name,
|
||||||
)
|
)
|
||||||
except JSON_ENCODE_EXCEPTIONS as ex:
|
except JSON_ENCODE_EXCEPTIONS as ex:
|
||||||
|
@ -119,7 +119,6 @@ def _default_recorder(hass):
|
|||||||
db_retry_wait=3,
|
db_retry_wait=3,
|
||||||
entity_filter=CONFIG_SCHEMA({DOMAIN: {}}),
|
entity_filter=CONFIG_SCHEMA({DOMAIN: {}}),
|
||||||
exclude_event_types=set(),
|
exclude_event_types=set(),
|
||||||
exclude_attributes_by_domain={},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2264,17 +2263,14 @@ async def test_connect_args_priority(hass: HomeAssistant, config_url) -> None:
|
|||||||
assert connect_params[0]["charset"] == "utf8mb4"
|
assert connect_params[0]["charset"] == "utf8mb4"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("core_state", [CoreState.starting, CoreState.running])
|
|
||||||
async def test_excluding_attributes_by_integration(
|
async def test_excluding_attributes_by_integration(
|
||||||
recorder_mock: Recorder,
|
recorder_mock: Recorder,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
core_state: CoreState,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that an integration's recorder platform can exclude attributes."""
|
"""Test that an entity can exclude attributes from being recorded."""
|
||||||
hass.state = core_state
|
|
||||||
state = "restoring_from_db"
|
state = "restoring_from_db"
|
||||||
attributes = {"test_attr": 5, "excluded": 10}
|
attributes = {"test_attr": 5, "excluded_component": 10, "excluded_integration": 20}
|
||||||
mock_platform(
|
mock_platform(
|
||||||
hass,
|
hass,
|
||||||
"fake_integration.recorder",
|
"fake_integration.recorder",
|
||||||
@ -2284,10 +2280,17 @@ async def test_excluding_attributes_by_integration(
|
|||||||
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {"component": "fake_integration"})
|
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {"component": "fake_integration"})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
class EntityWithExcludedAttributes(MockEntity):
|
||||||
|
_entity_component_unrecorded_attributes = frozenset({"excluded_component"})
|
||||||
|
_unrecorded_attributes = frozenset({"excluded_integration"})
|
||||||
|
|
||||||
entity_id = "test.fake_integration_recorder"
|
entity_id = "test.fake_integration_recorder"
|
||||||
platform = MockEntityPlatform(hass, platform_name="fake_integration")
|
entity_platform = MockEntityPlatform(hass, platform_name="fake_integration")
|
||||||
entity_platform = MockEntity(entity_id=entity_id, extra_state_attributes=attributes)
|
entity = EntityWithExcludedAttributes(
|
||||||
await platform.async_add_entities([entity_platform])
|
entity_id=entity_id,
|
||||||
|
extra_state_attributes=attributes,
|
||||||
|
)
|
||||||
|
await entity_platform.async_add_entities([entity])
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
@ -77,7 +77,7 @@ def test_from_event_to_db_state_attributes() -> None:
|
|||||||
dialect = SupportedDialect.MYSQL
|
dialect = SupportedDialect.MYSQL
|
||||||
|
|
||||||
db_attrs.shared_attrs = StateAttributes.shared_attrs_bytes_from_event(
|
db_attrs.shared_attrs = StateAttributes.shared_attrs_bytes_from_event(
|
||||||
event, {}, {}, dialect
|
event, {}, dialect
|
||||||
)
|
)
|
||||||
assert db_attrs.to_native() == attrs
|
assert db_attrs.to_native() == attrs
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user