Add async_track_state_reported_event to fix integration performance regression (#120622)

split from https://github.com/home-assistant/core/pull/120621
This commit is contained in:
J. Nick Koston 2024-06-26 22:04:27 -05:00 committed by GitHub
parent f189d87fe9
commit a5a631148e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 7 deletions

View file

@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Any, Concatenate, Generic, TypeVar
from homeassistant.const import ( from homeassistant.const import (
EVENT_CORE_CONFIG_UPDATE, EVENT_CORE_CONFIG_UPDATE,
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
EVENT_STATE_REPORTED,
MATCH_ALL, MATCH_ALL,
SUN_EVENT_SUNRISE, SUN_EVENT_SUNRISE,
SUN_EVENT_SUNSET, SUN_EVENT_SUNSET,
@ -26,6 +27,7 @@ from homeassistant.core import (
Event, Event,
# Explicit reexport of 'EventStateChangedData' for backwards compatibility # Explicit reexport of 'EventStateChangedData' for backwards compatibility
EventStateChangedData as EventStateChangedData, # noqa: PLC0414 EventStateChangedData as EventStateChangedData, # noqa: PLC0414
EventStateReportedData,
HassJob, HassJob,
HassJobType, HassJobType,
HomeAssistant, HomeAssistant,
@ -57,6 +59,9 @@ from .typing import TemplateVarsType
_TRACK_STATE_CHANGE_DATA: HassKey[_KeyedEventData[EventStateChangedData]] = HassKey( _TRACK_STATE_CHANGE_DATA: HassKey[_KeyedEventData[EventStateChangedData]] = HassKey(
"track_state_change_data" "track_state_change_data"
) )
_TRACK_STATE_REPORTED_DATA: HassKey[_KeyedEventData[EventStateReportedData]] = HassKey(
"track_state_reported_data"
)
_TRACK_STATE_ADDED_DOMAIN_DATA: HassKey[_KeyedEventData[EventStateChangedData]] = ( _TRACK_STATE_ADDED_DOMAIN_DATA: HassKey[_KeyedEventData[EventStateChangedData]] = (
HassKey("track_state_added_domain_data") HassKey("track_state_added_domain_data")
) )
@ -324,8 +329,8 @@ def async_track_state_change_event(
@callback @callback
def _async_dispatch_entity_id_event( def _async_dispatch_entity_id_event(
hass: HomeAssistant, hass: HomeAssistant,
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], callbacks: dict[str, list[HassJob[[Event[_TypedDictT]], Any]]],
event: Event[EventStateChangedData], event: Event[_TypedDictT],
) -> None: ) -> None:
"""Dispatch to listeners.""" """Dispatch to listeners."""
if not (callbacks_list := callbacks.get(event.data["entity_id"])): if not (callbacks_list := callbacks.get(event.data["entity_id"])):
@ -342,10 +347,10 @@ def _async_dispatch_entity_id_event(
@callback @callback
def _async_state_change_filter( def _async_state_filter(
hass: HomeAssistant, hass: HomeAssistant,
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], callbacks: dict[str, list[HassJob[[Event[_TypedDictT]], Any]]],
event_data: EventStateChangedData, event_data: _TypedDictT,
) -> bool: ) -> bool:
"""Filter state changes by entity_id.""" """Filter state changes by entity_id."""
return event_data["entity_id"] in callbacks return event_data["entity_id"] in callbacks
@ -355,7 +360,7 @@ _KEYED_TRACK_STATE_CHANGE = _KeyedEventTracker(
key=_TRACK_STATE_CHANGE_DATA, key=_TRACK_STATE_CHANGE_DATA,
event_type=EVENT_STATE_CHANGED, event_type=EVENT_STATE_CHANGED,
dispatcher_callable=_async_dispatch_entity_id_event, dispatcher_callable=_async_dispatch_entity_id_event,
filter_callable=_async_state_change_filter, filter_callable=_async_state_filter,
) )
@ -372,6 +377,26 @@ def _async_track_state_change_event(
) )
_KEYED_TRACK_STATE_REPORTED = _KeyedEventTracker(
key=_TRACK_STATE_REPORTED_DATA,
event_type=EVENT_STATE_REPORTED,
dispatcher_callable=_async_dispatch_entity_id_event,
filter_callable=_async_state_filter,
)
def async_track_state_reported_event(
hass: HomeAssistant,
entity_ids: str | Iterable[str],
action: Callable[[Event[EventStateReportedData]], Any],
job_type: HassJobType | None = None,
) -> CALLBACK_TYPE:
"""Track EVENT_STATE_REPORTED by entity_id without lowercasing."""
return _async_track_event(
_KEYED_TRACK_STATE_REPORTED, hass, entity_ids, action, job_type
)
@callback @callback
def _remove_empty_listener() -> None: def _remove_empty_listener() -> None:
"""Remove a listener that does nothing.""" """Remove a listener that does nothing."""

View file

@ -15,7 +15,13 @@ import pytest
from homeassistant.const import MATCH_ALL from homeassistant.const import MATCH_ALL
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback from homeassistant.core import (
Event,
EventStateChangedData,
EventStateReportedData,
HomeAssistant,
callback,
)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
@ -34,6 +40,7 @@ from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
async_track_state_change_filtered, async_track_state_change_filtered,
async_track_state_removed_domain, async_track_state_removed_domain,
async_track_state_reported_event,
async_track_sunrise, async_track_sunrise,
async_track_sunset, async_track_sunset,
async_track_template, async_track_template,
@ -4907,3 +4914,26 @@ async def test_track_point_in_time_repr(
assert "Exception in callback _TrackPointUTCTime" in caplog.text assert "Exception in callback _TrackPointUTCTime" in caplog.text
assert "._raise_exception" in caplog.text assert "._raise_exception" in caplog.text
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
async def test_async_track_state_reported_event(hass: HomeAssistant) -> None:
"""Test async_track_state_reported_event."""
tracker_called: list[ha.State] = []
@ha.callback
def single_run_callback(event: Event[EventStateReportedData]) -> None:
new_state = event.data["new_state"]
tracker_called.append(new_state)
unsub = async_track_state_reported_event(
hass, ["light.bowl", "light.top"], single_run_callback
)
hass.states.async_set("light.bowl", "on")
hass.states.async_set("light.top", "on")
await hass.async_block_till_done()
assert len(tracker_called) == 0
hass.states.async_set("light.bowl", "on")
hass.states.async_set("light.top", "on")
await hass.async_block_till_done()
assert len(tracker_called) == 2
unsub()