From 9e3869ae1c08f5293a2b130999843118395411d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Dec 2023 17:10:40 -1000 Subject: [PATCH] Avoid recreating ReadOnly dicts when attributes do not change (#106687) --- homeassistant/core.py | 13 ++++++++++++- tests/test_core.py | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 51cb3d4e496..cb17bd55805 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1409,7 +1409,13 @@ class State: self.entity_id = entity_id self.state = state - self.attributes = ReadOnlyDict(attributes or {}) + # State only creates and expects a ReadOnlyDict so + # there is no need to check for subclassing with + # isinstance here so we can use the faster type check. + if type(attributes) is not ReadOnlyDict: # noqa: E721 + self.attributes = ReadOnlyDict(attributes or {}) + else: + self.attributes = attributes self.last_updated = last_updated or dt_util.utcnow() self.last_changed = last_changed or self.last_updated self.context = context or Context() @@ -1828,6 +1834,11 @@ class StateMachine: else: now = dt_util.utcnow() + if same_attr: + if TYPE_CHECKING: + assert old_state is not None + attributes = old_state.attributes + state = State( entity_id, new_state, diff --git a/tests/test_core.py b/tests/test_core.py index 90b87068a5d..da76961c5be 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1157,6 +1157,26 @@ async def test_statemachine_force_update(hass: HomeAssistant) -> None: assert len(events) == 1 +async def test_statemachine_avoids_updating_attributes(hass: HomeAssistant) -> None: + """Test async_set avoids recreating ReadOnly dicts when possible.""" + attrs = {"some_attr": "attr_value"} + + hass.states.async_set("light.bowl", "off", attrs) + await hass.async_block_till_done() + + state = hass.states.get("light.bowl") + assert state.attributes == attrs + + hass.states.async_set("light.bowl", "on", attrs) + await hass.async_block_till_done() + + new_state = hass.states.get("light.bowl") + assert new_state.attributes == attrs + + assert new_state.attributes is state.attributes + assert isinstance(new_state.attributes, ReadOnlyDict) + + def test_service_call_repr() -> None: """Test ServiceCall repr.""" call = ha.ServiceCall("homeassistant", "start")