mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add capability to exclude all attributes from recording (#119725)
This commit is contained in:
parent
3cf52a4767
commit
753ab08b5e
@ -35,7 +35,12 @@ from sqlalchemy.ext.compiler import compiles
|
|||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, aliased, mapped_column, relationship
|
from sqlalchemy.orm import DeclarativeBase, Mapped, aliased, mapped_column, relationship
|
||||||
from sqlalchemy.types import TypeDecorator
|
from sqlalchemy.types import TypeDecorator
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
MATCH_ALL,
|
||||||
MAX_LENGTH_EVENT_EVENT_TYPE,
|
MAX_LENGTH_EVENT_EVENT_TYPE,
|
||||||
MAX_LENGTH_STATE_ENTITY_ID,
|
MAX_LENGTH_STATE_ENTITY_ID,
|
||||||
MAX_LENGTH_STATE_STATE,
|
MAX_LENGTH_STATE_STATE,
|
||||||
@ -584,10 +589,27 @@ class StateAttributes(Base):
|
|||||||
if (state := event.data["new_state"]) is None:
|
if (state := event.data["new_state"]) is None:
|
||||||
return b"{}"
|
return b"{}"
|
||||||
if state_info := state.state_info:
|
if state_info := state.state_info:
|
||||||
|
unrecorded_attributes = state_info["unrecorded_attributes"]
|
||||||
exclude_attrs = {
|
exclude_attrs = {
|
||||||
*ALL_DOMAIN_EXCLUDE_ATTRS,
|
*ALL_DOMAIN_EXCLUDE_ATTRS,
|
||||||
*state_info["unrecorded_attributes"],
|
*unrecorded_attributes,
|
||||||
}
|
}
|
||||||
|
if MATCH_ALL in unrecorded_attributes:
|
||||||
|
# Don't exclude device class, state class, unit of measurement
|
||||||
|
# or friendly name when using the MATCH_ALL exclude constant
|
||||||
|
_exclude_attributes = {
|
||||||
|
k: v
|
||||||
|
for k, v in state.attributes.items()
|
||||||
|
if k
|
||||||
|
not in (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exclude_attrs.update(_exclude_attributes)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
exclude_attrs = ALL_DOMAIN_EXCLUDE_ATTRS
|
exclude_attrs = ALL_DOMAIN_EXCLUDE_ATTRS
|
||||||
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
||||||
|
@ -54,7 +54,12 @@ from homeassistant.components.recorder.models import (
|
|||||||
ulid_to_bytes_or_none,
|
ulid_to_bytes_or_none,
|
||||||
uuid_hex_to_bytes_or_none,
|
uuid_hex_to_bytes_or_none,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
MATCH_ALL,
|
||||||
MAX_LENGTH_EVENT_EVENT_TYPE,
|
MAX_LENGTH_EVENT_EVENT_TYPE,
|
||||||
MAX_LENGTH_STATE_ENTITY_ID,
|
MAX_LENGTH_STATE_ENTITY_ID,
|
||||||
MAX_LENGTH_STATE_STATE,
|
MAX_LENGTH_STATE_STATE,
|
||||||
@ -577,10 +582,27 @@ class StateAttributes(Base):
|
|||||||
if state is None:
|
if state is None:
|
||||||
return b"{}"
|
return b"{}"
|
||||||
if state_info := state.state_info:
|
if state_info := state.state_info:
|
||||||
|
unrecorded_attributes = state_info["unrecorded_attributes"]
|
||||||
exclude_attrs = {
|
exclude_attrs = {
|
||||||
*ALL_DOMAIN_EXCLUDE_ATTRS,
|
*ALL_DOMAIN_EXCLUDE_ATTRS,
|
||||||
*state_info["unrecorded_attributes"],
|
*unrecorded_attributes,
|
||||||
}
|
}
|
||||||
|
if MATCH_ALL in unrecorded_attributes:
|
||||||
|
# Don't exclude device class, state class, unit of measurement
|
||||||
|
# or friendly name when using the MATCH_ALL exclude constant
|
||||||
|
_exclude_attributes = {
|
||||||
|
k: v
|
||||||
|
for k, v in state.attributes.items()
|
||||||
|
if k
|
||||||
|
not in (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exclude_attrs.update(_exclude_attributes)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
exclude_attrs = ALL_DOMAIN_EXCLUDE_ATTRS
|
exclude_attrs = ALL_DOMAIN_EXCLUDE_ATTRS
|
||||||
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes
|
||||||
|
@ -2420,6 +2420,71 @@ async def test_excluding_attributes_by_integration(
|
|||||||
assert state.as_dict() == expected.as_dict()
|
assert state.as_dict() == expected.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_excluding_all_attributes_by_integration(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
setup_recorder: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test that an entity can exclude all attributes from being recorded using MATCH_ALL."""
|
||||||
|
state = "restoring_from_db"
|
||||||
|
attributes = {
|
||||||
|
"test_attr": 5,
|
||||||
|
"excluded_component": 10,
|
||||||
|
"excluded_integration": 20,
|
||||||
|
"device_class": "test",
|
||||||
|
"state_class": "test",
|
||||||
|
"friendly_name": "Test entity",
|
||||||
|
"unit_of_measurement": "mm",
|
||||||
|
}
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"fake_integration.recorder",
|
||||||
|
Mock(exclude_attributes=lambda hass: {"excluded"}),
|
||||||
|
)
|
||||||
|
hass.config.components.add("fake_integration")
|
||||||
|
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {"component": "fake_integration"})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
class EntityWithExcludedAttributes(MockEntity):
|
||||||
|
_unrecorded_attributes = frozenset({MATCH_ALL})
|
||||||
|
|
||||||
|
entity_id = "test.fake_integration_recorder"
|
||||||
|
entity_platform = MockEntityPlatform(hass, platform_name="fake_integration")
|
||||||
|
entity = EntityWithExcludedAttributes(
|
||||||
|
entity_id=entity_id,
|
||||||
|
extra_state_attributes=attributes,
|
||||||
|
)
|
||||||
|
await entity_platform.async_add_entities([entity])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
with session_scope(hass=hass, read_only=True) as session:
|
||||||
|
db_states = []
|
||||||
|
for db_state, db_state_attributes, states_meta in (
|
||||||
|
session.query(States, StateAttributes, StatesMeta)
|
||||||
|
.outerjoin(
|
||||||
|
StateAttributes, States.attributes_id == StateAttributes.attributes_id
|
||||||
|
)
|
||||||
|
.outerjoin(StatesMeta, States.metadata_id == StatesMeta.metadata_id)
|
||||||
|
):
|
||||||
|
db_state.entity_id = states_meta.entity_id
|
||||||
|
db_states.append(db_state)
|
||||||
|
state = db_state.to_native()
|
||||||
|
state.attributes = db_state_attributes.to_native()
|
||||||
|
assert len(db_states) == 1
|
||||||
|
assert db_states[0].event_id is None
|
||||||
|
|
||||||
|
expected = _state_with_context(hass, entity_id)
|
||||||
|
expected.attributes = {
|
||||||
|
"device_class": "test",
|
||||||
|
"state_class": "test",
|
||||||
|
"friendly_name": "Test entity",
|
||||||
|
"unit_of_measurement": "mm",
|
||||||
|
}
|
||||||
|
assert state.as_dict() == expected.as_dict()
|
||||||
|
|
||||||
|
|
||||||
async def test_lru_increases_with_many_entities(
|
async def test_lru_increases_with_many_entities(
|
||||||
small_cache_size: None, hass: HomeAssistant, setup_recorder: None
|
small_cache_size: None, hass: HomeAssistant, setup_recorder: None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user