mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Avoid creating unneeded Context and Event objects when firing events (#113798)
* Avoid creating unneeded Context and Event objects when firing events * Add test --------- Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
638020f168
commit
d31124d5d4
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import ItemsView
|
from collections.abc import ItemsView, Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -101,30 +101,18 @@ async def async_attach_trigger(
|
|||||||
job = HassJob(action, f"event trigger {trigger_info}")
|
job = HassJob(action, f"event trigger {trigger_info}")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def filter_event(event: Event) -> bool:
|
def filter_event(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Filter events."""
|
"""Filter events."""
|
||||||
try:
|
try:
|
||||||
# Check that the event data and context match the configured
|
# Check that the event data and context match the configured
|
||||||
# schema if one was provided
|
# schema if one was provided
|
||||||
if event_data_items:
|
if event_data_items:
|
||||||
# Fast path for simple items comparison
|
# Fast path for simple items comparison
|
||||||
if not (event.data.items() >= event_data_items):
|
if not (event_data.items() >= event_data_items):
|
||||||
return False
|
return False
|
||||||
elif event_data_schema:
|
elif event_data_schema:
|
||||||
# Slow path for schema validation
|
# Slow path for schema validation
|
||||||
event_data_schema(event.data)
|
event_data_schema(event_data)
|
||||||
|
|
||||||
if event_context_items:
|
|
||||||
# Fast path for simple items comparison
|
|
||||||
# This is safe because we do not mutate the event context
|
|
||||||
# pylint: disable-next=protected-access
|
|
||||||
if not (event.context._as_dict.items() >= event_context_items):
|
|
||||||
return False
|
|
||||||
elif event_context_schema:
|
|
||||||
# Slow path for schema validation
|
|
||||||
# This is safe because we make a copy of the event context
|
|
||||||
# pylint: disable-next=protected-access
|
|
||||||
event_context_schema(dict(event.context._as_dict))
|
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
# If event doesn't match, skip event
|
# If event doesn't match, skip event
|
||||||
return False
|
return False
|
||||||
@ -133,6 +121,22 @@ async def async_attach_trigger(
|
|||||||
@callback
|
@callback
|
||||||
def handle_event(event: Event) -> None:
|
def handle_event(event: Event) -> None:
|
||||||
"""Listen for events and calls the action when data matches."""
|
"""Listen for events and calls the action when data matches."""
|
||||||
|
if event_context_items:
|
||||||
|
# Fast path for simple items comparison
|
||||||
|
# This is safe because we do not mutate the event context
|
||||||
|
# pylint: disable-next=protected-access
|
||||||
|
if not (event.context._as_dict.items() >= event_context_items):
|
||||||
|
return
|
||||||
|
elif event_context_schema:
|
||||||
|
try:
|
||||||
|
# Slow path for schema validation
|
||||||
|
# This is safe because we make a copy of the event context
|
||||||
|
# pylint: disable-next=protected-access
|
||||||
|
event_context_schema(dict(event.context._as_dict))
|
||||||
|
except vol.Invalid:
|
||||||
|
# If event doesn't match, skip event
|
||||||
|
return
|
||||||
|
|
||||||
hass.async_run_hass_job(
|
hass.async_run_hass_job(
|
||||||
job,
|
job,
|
||||||
{
|
{
|
||||||
@ -146,9 +150,10 @@ async def async_attach_trigger(
|
|||||||
event.context,
|
event.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
event_filter = filter_event if event_data_items or event_data_schema else None
|
||||||
removes = [
|
removes = [
|
||||||
hass.bus.async_listen(
|
hass.bus.async_listen(
|
||||||
event_type, handle_event, event_filter=filter_event, run_immediately=True
|
event_type, handle_event, event_filter=event_filter, run_immediately=True
|
||||||
)
|
)
|
||||||
for event_type in event_types
|
for event_type in event_types
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
"""Publish simple item state changes via MQTT."""
|
"""Publish simple item state changes via MQTT."""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -90,9 +92,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
@callback
|
@callback
|
||||||
def _ha_started(hass: HomeAssistant) -> None:
|
def _ha_started(hass: HomeAssistant) -> None:
|
||||||
@callback
|
@callback
|
||||||
def _event_filter(evt: Event) -> bool:
|
def _event_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
entity_id: str = evt.data["entity_id"]
|
entity_id: str = event_data["entity_id"]
|
||||||
new_state: State | None = evt.data["new_state"]
|
new_state: State | None = event_data["new_state"]
|
||||||
if new_state is None:
|
if new_state is None:
|
||||||
return False
|
return False
|
||||||
if not publish_filter(entity_id):
|
if not publish_filter(entity_id):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Mapping
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Self
|
from typing import Any, Self
|
||||||
|
|
||||||
@ -248,11 +248,11 @@ class PersonStorageCollection(collection.DictStorageCollection):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _entity_registry_filter(self, event: Event) -> bool:
|
def _entity_registry_filter(self, event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Filter entity registry events."""
|
"""Filter entity registry events."""
|
||||||
return (
|
return (
|
||||||
event.data["action"] == "remove"
|
event_data["action"] == "remove"
|
||||||
and split_entity_id(event.data[ATTR_ENTITY_ID])[0] == "device_tracker"
|
and split_entity_id(event_data[ATTR_ENTITY_ID])[0] == "device_tracker"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _entity_registry_updated(self, event: Event) -> None:
|
async def _entity_registry_updated(self, event: Event) -> None:
|
||||||
|
@ -321,7 +321,7 @@ class Events(Base):
|
|||||||
EventOrigin(self.origin)
|
EventOrigin(self.origin)
|
||||||
if self.origin
|
if self.origin
|
||||||
else EVENT_ORIGIN_ORDER[self.origin_idx or 0],
|
else EVENT_ORIGIN_ORDER[self.origin_idx or 0],
|
||||||
dt_util.utc_from_timestamp(self.time_fired_ts or 0),
|
self.time_fired_ts or 0,
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
except JSON_DECODE_EXCEPTIONS:
|
except JSON_DECODE_EXCEPTIONS:
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Recorder entity registry helper."""
|
"""Recorder entity registry helper."""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@ -29,9 +31,9 @@ def async_setup(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def entity_registry_changed_filter(event: Event) -> bool:
|
def entity_registry_changed_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Handle entity_id changed filter."""
|
"""Handle entity_id changed filter."""
|
||||||
return event.data["action"] == "update" and "old_entity_id" in event.data
|
return event_data["action"] == "update" and "old_entity_id" in event_data
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _setup_entity_registry_event_handler(hass: HomeAssistant) -> None:
|
def _setup_entity_registry_event_handler(hass: HomeAssistant) -> None:
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
"""Provides device automations for Tasmota."""
|
"""Provides device automations for Tasmota."""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from hatasmota.const import AUTOMATION_TYPE_TRIGGER
|
from hatasmota.const import AUTOMATION_TYPE_TRIGGER
|
||||||
from hatasmota.models import DiscoveryHashType
|
from hatasmota.models import DiscoveryHashType
|
||||||
from hatasmota.trigger import TasmotaTrigger
|
from hatasmota.trigger import TasmotaTrigger
|
||||||
@ -27,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> N
|
|||||||
await async_remove_automations(hass, event.data["device_id"])
|
await async_remove_automations(hass, event.data["device_id"])
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_device_removed_filter(event: Event) -> bool:
|
def _async_device_removed_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Filter device registry events."""
|
"""Filter device registry events."""
|
||||||
return event.data["action"] == "remove"
|
return event_data["action"] == "remove"
|
||||||
|
|
||||||
async def async_discover(
|
async def async_discover(
|
||||||
tasmota_automation: TasmotaTrigger, discovery_hash: DiscoveryHashType
|
tasmota_automation: TasmotaTrigger, discovery_hash: DiscoveryHashType
|
||||||
|
@ -97,7 +97,7 @@ class VoIPDevices:
|
|||||||
self.hass.bus.async_listen(
|
self.hass.bus.async_listen(
|
||||||
dr.EVENT_DEVICE_REGISTRY_UPDATED,
|
dr.EVENT_DEVICE_REGISTRY_UPDATED,
|
||||||
async_device_removed,
|
async_device_removed,
|
||||||
callback(lambda ev: ev.data.get("action") == "remove"),
|
callback(lambda event_data: event_data.get("action") == "remove"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2519,16 +2519,16 @@ class EntityRegistryDisabledHandler:
|
|||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_entry_updated_filter(event: Event) -> bool:
|
def _handle_entry_updated_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Handle entity registry entry update filter.
|
"""Handle entity registry entry update filter.
|
||||||
|
|
||||||
Only handle changes to "disabled_by".
|
Only handle changes to "disabled_by".
|
||||||
If "disabled_by" was CONFIG_ENTRY, reload is not needed.
|
If "disabled_by" was CONFIG_ENTRY, reload is not needed.
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
event.data["action"] != "update"
|
event_data["action"] != "update"
|
||||||
or "disabled_by" not in event.data["changes"]
|
or "disabled_by" not in event_data["changes"]
|
||||||
or event.data["changes"]["disabled_by"]
|
or event_data["changes"]["disabled_by"]
|
||||||
is entity_registry.RegistryEntryDisabler.CONFIG_ENTRY
|
is entity_registry.RegistryEntryDisabler.CONFIG_ENTRY
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
@ -67,6 +67,7 @@ from .const import (
|
|||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STARTED,
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
EVENT_LOGGING_CHANGED,
|
||||||
EVENT_SERVICE_REGISTERED,
|
EVENT_SERVICE_REGISTERED,
|
||||||
EVENT_SERVICE_REMOVED,
|
EVENT_SERVICE_REMOVED,
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
@ -1215,24 +1216,24 @@ class Event(Generic[_DataT]):
|
|||||||
event_type: str,
|
event_type: str,
|
||||||
data: _DataT | None = None,
|
data: _DataT | None = None,
|
||||||
origin: EventOrigin = EventOrigin.local,
|
origin: EventOrigin = EventOrigin.local,
|
||||||
time_fired: datetime.datetime | None = None,
|
time_fired_timestamp: float | None = None,
|
||||||
context: Context | None = None,
|
context: Context | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a new event."""
|
"""Initialize a new event."""
|
||||||
self.event_type = event_type
|
self.event_type = event_type
|
||||||
self.data: _DataT = data or {} # type: ignore[assignment]
|
self.data: _DataT = data or {} # type: ignore[assignment]
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.time_fired = time_fired or dt_util.utcnow()
|
self.time_fired_timestamp = time_fired_timestamp or time.time()
|
||||||
if not context:
|
if not context:
|
||||||
context = Context(id=ulid_at_time(self.time_fired.timestamp()))
|
context = Context(id=ulid_at_time(self.time_fired_timestamp))
|
||||||
self.context = context
|
self.context = context
|
||||||
if not context.origin_event:
|
if not context.origin_event:
|
||||||
context.origin_event = self
|
context.origin_event = self
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def time_fired_timestamp(self) -> float:
|
def time_fired(self) -> datetime.datetime:
|
||||||
"""Return time fired as a timestamp."""
|
"""Return time fired as a timestamp."""
|
||||||
return self.time_fired.timestamp()
|
return dt_util.utc_from_timestamp(self.time_fired_timestamp)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _as_dict(self) -> dict[str, Any]:
|
def _as_dict(self) -> dict[str, Any]:
|
||||||
@ -1282,18 +1283,22 @@ class Event(Generic[_DataT]):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""Return the representation."""
|
"""Return the representation."""
|
||||||
if self.data:
|
return _event_repr(self.event_type, self.origin, self.data)
|
||||||
return (
|
|
||||||
f"<Event {self.event_type}[{str(self.origin)[0]}]:"
|
|
||||||
f" {util.repr_helper(self.data)}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
return f"<Event {self.event_type}[{str(self.origin)[0]}]>"
|
|
||||||
|
def _event_repr(
|
||||||
|
event_type: str, origin: EventOrigin, data: Mapping[str, Any] | None
|
||||||
|
) -> str:
|
||||||
|
"""Return the representation."""
|
||||||
|
if data:
|
||||||
|
return f"<Event {event_type}[{str(origin)[0]}]: {util.repr_helper(data)}>"
|
||||||
|
|
||||||
|
return f"<Event {event_type}[{str(origin)[0]}]>"
|
||||||
|
|
||||||
|
|
||||||
_FilterableJobType = tuple[
|
_FilterableJobType = tuple[
|
||||||
HassJob[[Event[_DataT]], Coroutine[Any, Any, None] | None], # job
|
HassJob[[Event[_DataT]], Coroutine[Any, Any, None] | None], # job
|
||||||
Callable[[Event[_DataT]], bool] | None, # event_filter
|
Callable[[_DataT], bool] | None, # event_filter
|
||||||
bool, # run_immediately
|
bool, # run_immediately
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1325,7 +1330,7 @@ class _OneTimeListener:
|
|||||||
class EventBus:
|
class EventBus:
|
||||||
"""Allow the firing of and listening for events."""
|
"""Allow the firing of and listening for events."""
|
||||||
|
|
||||||
__slots__ = ("_listeners", "_match_all_listeners", "_hass")
|
__slots__ = ("_debug", "_hass", "_listeners", "_match_all_listeners")
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant) -> None:
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
"""Initialize a new event bus."""
|
"""Initialize a new event bus."""
|
||||||
@ -1333,6 +1338,15 @@ class EventBus:
|
|||||||
self._match_all_listeners: list[_FilterableJobType[Any]] = []
|
self._match_all_listeners: list[_FilterableJobType[Any]] = []
|
||||||
self._listeners[MATCH_ALL] = self._match_all_listeners
|
self._listeners[MATCH_ALL] = self._match_all_listeners
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
|
self._async_logging_changed()
|
||||||
|
self.async_listen(
|
||||||
|
EVENT_LOGGING_CHANGED, self._async_logging_changed, run_immediately=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_logging_changed(self, event: Event | None = None) -> None:
|
||||||
|
"""Handle logging change."""
|
||||||
|
self._debug = _LOGGER.isEnabledFor(logging.DEBUG)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_listeners(self) -> dict[str, int]:
|
def async_listeners(self) -> dict[str, int]:
|
||||||
@ -1366,7 +1380,7 @@ class EventBus:
|
|||||||
event_data: Mapping[str, Any] | None = None,
|
event_data: Mapping[str, Any] | None = None,
|
||||||
origin: EventOrigin = EventOrigin.local,
|
origin: EventOrigin = EventOrigin.local,
|
||||||
context: Context | None = None,
|
context: Context | None = None,
|
||||||
time_fired: datetime.datetime | None = None,
|
time_fired: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fire an event.
|
"""Fire an event.
|
||||||
|
|
||||||
@ -1376,30 +1390,57 @@ class EventBus:
|
|||||||
raise MaxLengthExceeded(
|
raise MaxLengthExceeded(
|
||||||
event_type, "event_type", MAX_LENGTH_EVENT_EVENT_TYPE
|
event_type, "event_type", MAX_LENGTH_EVENT_EVENT_TYPE
|
||||||
)
|
)
|
||||||
|
return self._async_fire(event_type, event_data, origin, context, time_fired)
|
||||||
|
|
||||||
listeners = self._listeners.get(event_type, [])
|
@callback
|
||||||
match_all_listeners = self._match_all_listeners
|
def _async_fire(
|
||||||
|
self,
|
||||||
|
event_type: str,
|
||||||
|
event_data: Mapping[str, Any] | None = None,
|
||||||
|
origin: EventOrigin = EventOrigin.local,
|
||||||
|
context: Context | None = None,
|
||||||
|
time_fired: float | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Fire an event.
|
||||||
|
|
||||||
event = Event(event_type, event_data, origin, time_fired, context)
|
This method must be run in the event loop.
|
||||||
|
"""
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
if self._debug:
|
||||||
_LOGGER.debug("Bus:Handling %s", event)
|
_LOGGER.debug(
|
||||||
|
"Bus:Handling %s", _event_repr(event_type, origin, event_data)
|
||||||
if not listeners and not match_all_listeners:
|
)
|
||||||
return
|
|
||||||
|
|
||||||
|
listeners = self._listeners.get(event_type)
|
||||||
# EVENT_HOMEASSISTANT_CLOSE should not be sent to MATCH_ALL listeners
|
# EVENT_HOMEASSISTANT_CLOSE should not be sent to MATCH_ALL listeners
|
||||||
if event_type != EVENT_HOMEASSISTANT_CLOSE:
|
if event_type != EVENT_HOMEASSISTANT_CLOSE:
|
||||||
listeners = match_all_listeners + listeners
|
if listeners:
|
||||||
|
listeners = self._match_all_listeners + listeners
|
||||||
|
else:
|
||||||
|
listeners = self._match_all_listeners.copy()
|
||||||
|
if not listeners:
|
||||||
|
return
|
||||||
|
|
||||||
|
event: Event | None = None
|
||||||
|
|
||||||
for job, event_filter, run_immediately in listeners:
|
for job, event_filter, run_immediately in listeners:
|
||||||
if event_filter is not None:
|
if event_filter is not None:
|
||||||
try:
|
try:
|
||||||
if not event_filter(event):
|
if event_data is None or not event_filter(event_data):
|
||||||
continue
|
continue
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error in event filter")
|
_LOGGER.exception("Error in event filter")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
event = Event(
|
||||||
|
event_type,
|
||||||
|
event_data,
|
||||||
|
origin,
|
||||||
|
time_fired,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
if run_immediately:
|
if run_immediately:
|
||||||
try:
|
try:
|
||||||
self._hass.async_run_hass_job(job, event)
|
self._hass.async_run_hass_job(job, event)
|
||||||
@ -1433,7 +1474,7 @@ class EventBus:
|
|||||||
self,
|
self,
|
||||||
event_type: str,
|
event_type: str,
|
||||||
listener: Callable[[Event[_DataT]], Coroutine[Any, Any, None] | None],
|
listener: Callable[[Event[_DataT]], Coroutine[Any, Any, None] | None],
|
||||||
event_filter: Callable[[Event[_DataT]], bool] | None = None,
|
event_filter: Callable[[_DataT], bool] | None = None,
|
||||||
run_immediately: bool = False,
|
run_immediately: bool = False,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Listen for all events or events of a specific type.
|
"""Listen for all events or events of a specific type.
|
||||||
@ -1952,7 +1993,7 @@ class StateMachine:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
old_state.expire()
|
old_state.expire()
|
||||||
self._bus.async_fire(
|
self._bus._async_fire( # pylint: disable=protected-access
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
{"entity_id": entity_id, "old_state": old_state, "new_state": None},
|
{"entity_id": entity_id, "old_state": old_state, "new_state": None},
|
||||||
context=context,
|
context=context,
|
||||||
@ -2047,32 +2088,35 @@ class StateMachine:
|
|||||||
same_attr = old_state.attributes == attributes
|
same_attr = old_state.attributes == attributes
|
||||||
last_changed = old_state.last_changed if same_state else None
|
last_changed = old_state.last_changed if same_state else None
|
||||||
|
|
||||||
|
# It is much faster to convert a timestamp to a utc datetime object
|
||||||
|
# than converting a utc datetime object to a timestamp since cpython
|
||||||
|
# does not have a fast path for handling the UTC timezone and has to do
|
||||||
|
# multiple local timezone conversions.
|
||||||
|
#
|
||||||
|
# from_timestamp implementation:
|
||||||
|
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L2936
|
||||||
|
#
|
||||||
|
# timestamp implementation:
|
||||||
|
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6387
|
||||||
|
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6323
|
||||||
|
timestamp = time.time()
|
||||||
|
now = dt_util.utc_from_timestamp(timestamp)
|
||||||
|
|
||||||
if same_state and same_attr:
|
if same_state and same_attr:
|
||||||
return
|
return
|
||||||
|
|
||||||
if context is None:
|
if context is None:
|
||||||
# It is much faster to convert a timestamp to a utc datetime object
|
if TYPE_CHECKING:
|
||||||
# than converting a utc datetime object to a timestamp since cpython
|
assert timestamp is not None
|
||||||
# does not have a fast path for handling the UTC timezone and has to do
|
|
||||||
# multiple local timezone conversions.
|
|
||||||
#
|
|
||||||
# from_timestamp implementation:
|
|
||||||
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L2936
|
|
||||||
#
|
|
||||||
# timestamp implementation:
|
|
||||||
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6387
|
|
||||||
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6323
|
|
||||||
timestamp = time.time()
|
|
||||||
now = dt_util.utc_from_timestamp(timestamp)
|
|
||||||
context = Context(id=ulid_at_time(timestamp))
|
context = Context(id=ulid_at_time(timestamp))
|
||||||
else:
|
|
||||||
now = dt_util.utcnow()
|
|
||||||
|
|
||||||
if same_attr:
|
if same_attr:
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert old_state is not None
|
assert old_state is not None
|
||||||
attributes = old_state.attributes
|
attributes = old_state.attributes
|
||||||
|
|
||||||
|
# This is intentionally called with positional only arguments for performance
|
||||||
|
# reasons
|
||||||
state = State(
|
state = State(
|
||||||
entity_id,
|
entity_id,
|
||||||
new_state,
|
new_state,
|
||||||
@ -2086,11 +2130,11 @@ class StateMachine:
|
|||||||
if old_state is not None:
|
if old_state is not None:
|
||||||
old_state.expire()
|
old_state.expire()
|
||||||
self._states[entity_id] = state
|
self._states[entity_id] = state
|
||||||
self._bus.async_fire(
|
self._bus._async_fire( # pylint: disable=protected-access
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
{"entity_id": entity_id, "old_state": old_state, "new_state": state},
|
{"entity_id": entity_id, "old_state": old_state, "new_state": state},
|
||||||
context=context,
|
context=context,
|
||||||
time_fired=now,
|
time_fired=timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2429,7 +2473,7 @@ class ServiceRegistry:
|
|||||||
domain, service, processed_data, context, return_response
|
domain, service, processed_data, context, return_response
|
||||||
)
|
)
|
||||||
|
|
||||||
self._hass.bus.async_fire(
|
self._hass.bus._async_fire( # pylint: disable=protected-access
|
||||||
EVENT_CALL_SERVICE,
|
EVENT_CALL_SERVICE,
|
||||||
{
|
{
|
||||||
ATTR_DOMAIN: domain,
|
ATTR_DOMAIN: domain,
|
||||||
|
@ -314,10 +314,11 @@ class AreaRegistry(BaseRegistry):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _removed_from_registry_filter(
|
def _removed_from_registry_filter(
|
||||||
event: fr.EventFloorRegistryUpdated | lr.EventLabelRegistryUpdated,
|
event_data: fr.EventFloorRegistryUpdatedData
|
||||||
|
| lr.EventLabelRegistryUpdatedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter all except for the item removed from registry events."""
|
"""Filter all except for the item removed from registry events."""
|
||||||
return event.data["action"] == "remove"
|
return event_data["action"] == "remove"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_floor_registry_update(event: fr.EventFloorRegistryUpdated) -> None:
|
def _handle_floor_registry_update(event: fr.EventFloorRegistryUpdated) -> None:
|
||||||
|
@ -1145,10 +1145,10 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _label_removed_from_registry_filter(
|
def _label_removed_from_registry_filter(
|
||||||
event: lr.EventLabelRegistryUpdated,
|
event_data: lr.EventLabelRegistryUpdatedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter all except for the remove action from label registry events."""
|
"""Filter all except for the remove action from label registry events."""
|
||||||
return event.data["action"] == "remove"
|
return event_data["action"] == "remove"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None:
|
def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None:
|
||||||
@ -1178,12 +1178,12 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None:
|
|||||||
debounced_cleanup.async_schedule_call()
|
debounced_cleanup.async_schedule_call()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def entity_registry_changed_filter(event: Event) -> bool:
|
def entity_registry_changed_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Handle entity updated or removed filter."""
|
"""Handle entity updated or removed filter."""
|
||||||
if (
|
if (
|
||||||
event.data["action"] == "update"
|
event_data["action"] == "update"
|
||||||
and "device_id" not in event.data["changes"]
|
and "device_id" not in event_data["changes"]
|
||||||
) or event.data["action"] == "create":
|
) or event_data["action"] == "create":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1431,10 +1431,11 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _removed_from_registry_filter(
|
def _removed_from_registry_filter(
|
||||||
event: lr.EventLabelRegistryUpdated | cr.EventCategoryRegistryUpdated,
|
event_data: lr.EventLabelRegistryUpdatedData
|
||||||
|
| cr.EventCategoryRegistryUpdatedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter all except for the remove action from registry events."""
|
"""Filter all except for the remove action from registry events."""
|
||||||
return event.data["action"] == "remove"
|
return event_data["action"] == "remove"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None:
|
def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None:
|
||||||
@ -1488,9 +1489,9 @@ def _async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) -
|
|||||||
"""Set up the entity restore mechanism."""
|
"""Set up the entity restore mechanism."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def cleanup_restored_states_filter(event: Event) -> bool:
|
def cleanup_restored_states_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Clean up restored states filter."""
|
"""Clean up restored states filter."""
|
||||||
return bool(event.data["action"] == "remove")
|
return bool(event_data["action"] == "remove")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def cleanup_restored_states(event: Event) -> None:
|
def cleanup_restored_states(event: Event) -> None:
|
||||||
|
@ -109,7 +109,7 @@ class _KeyedEventTracker(Generic[_TypedDictT]):
|
|||||||
[
|
[
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
dict[str, list[HassJob[[Event[_TypedDictT]], Any]]],
|
dict[str, list[HassJob[[Event[_TypedDictT]], Any]]],
|
||||||
Event[_TypedDictT],
|
_TypedDictT,
|
||||||
],
|
],
|
||||||
bool,
|
bool,
|
||||||
]
|
]
|
||||||
@ -237,11 +237,11 @@ def async_track_state_change(
|
|||||||
job = HassJob(action, f"track state change {entity_ids} {from_state} {to_state}")
|
job = HassJob(action, f"track state change {entity_ids} {from_state} {to_state}")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def state_change_filter(event: Event[EventStateChangedData]) -> bool:
|
def state_change_filter(event_data: EventStateChangedData) -> bool:
|
||||||
"""Handle specific state changes."""
|
"""Handle specific state changes."""
|
||||||
if from_state is not None:
|
if from_state is not None:
|
||||||
old_state_str: str | None = None
|
old_state_str: str | None = None
|
||||||
if (old_state := event.data["old_state"]) is not None:
|
if (old_state := event_data["old_state"]) is not None:
|
||||||
old_state_str = old_state.state
|
old_state_str = old_state.state
|
||||||
|
|
||||||
if not match_from_state(old_state_str):
|
if not match_from_state(old_state_str):
|
||||||
@ -249,7 +249,7 @@ def async_track_state_change(
|
|||||||
|
|
||||||
if to_state is not None:
|
if to_state is not None:
|
||||||
new_state_str: str | None = None
|
new_state_str: str | None = None
|
||||||
if (new_state := event.data["new_state"]) is not None:
|
if (new_state := event_data["new_state"]) is not None:
|
||||||
new_state_str = new_state.state
|
new_state_str = new_state.state
|
||||||
|
|
||||||
if not match_to_state(new_state_str):
|
if not match_to_state(new_state_str):
|
||||||
@ -270,7 +270,7 @@ def async_track_state_change(
|
|||||||
@callback
|
@callback
|
||||||
def state_change_listener(event: Event[EventStateChangedData]) -> None:
|
def state_change_listener(event: Event[EventStateChangedData]) -> None:
|
||||||
"""Handle specific state changes."""
|
"""Handle specific state changes."""
|
||||||
if not state_change_filter(event):
|
if not state_change_filter(event.data):
|
||||||
return
|
return
|
||||||
|
|
||||||
state_change_dispatcher(event)
|
state_change_dispatcher(event)
|
||||||
@ -341,10 +341,10 @@ def _async_dispatch_entity_id_event(
|
|||||||
def _async_state_change_filter(
|
def _async_state_change_filter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
||||||
event: Event[EventStateChangedData],
|
event_data: EventStateChangedData,
|
||||||
) -> 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
|
||||||
|
|
||||||
|
|
||||||
_KEYED_TRACK_STATE_CHANGE = _KeyedEventTracker(
|
_KEYED_TRACK_STATE_CHANGE = _KeyedEventTracker(
|
||||||
@ -473,10 +473,10 @@ def _async_dispatch_old_entity_id_or_entity_id_event(
|
|||||||
def _async_entity_registry_updated_filter(
|
def _async_entity_registry_updated_filter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbacks: dict[str, list[HassJob[[Event[EventEntityRegistryUpdatedData]], Any]]],
|
callbacks: dict[str, list[HassJob[[Event[EventEntityRegistryUpdatedData]], Any]]],
|
||||||
event: Event[EventEntityRegistryUpdatedData],
|
event_data: EventEntityRegistryUpdatedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter entity registry updates by entity_id."""
|
"""Filter entity registry updates by entity_id."""
|
||||||
return event.data.get("old_entity_id", event.data["entity_id"]) in callbacks
|
return event_data.get("old_entity_id", event_data["entity_id"]) in callbacks
|
||||||
|
|
||||||
|
|
||||||
_KEYED_TRACK_ENTITY_REGISTRY_UPDATED = _KeyedEventTracker(
|
_KEYED_TRACK_ENTITY_REGISTRY_UPDATED = _KeyedEventTracker(
|
||||||
@ -512,10 +512,10 @@ def async_track_entity_registry_updated_event(
|
|||||||
def _async_device_registry_updated_filter(
|
def _async_device_registry_updated_filter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbacks: dict[str, list[HassJob[[Event[EventDeviceRegistryUpdatedData]], Any]]],
|
callbacks: dict[str, list[HassJob[[Event[EventDeviceRegistryUpdatedData]], Any]]],
|
||||||
event: Event[EventDeviceRegistryUpdatedData],
|
event_data: EventDeviceRegistryUpdatedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter device registry updates by device_id."""
|
"""Filter device registry updates by device_id."""
|
||||||
return event.data["device_id"] in callbacks
|
return event_data["device_id"] in callbacks
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -585,12 +585,12 @@ def _async_dispatch_domain_event(
|
|||||||
def _async_domain_added_filter(
|
def _async_domain_added_filter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
||||||
event: Event[EventStateChangedData],
|
event_data: EventStateChangedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter state changes by entity_id."""
|
"""Filter state changes by entity_id."""
|
||||||
return event.data["old_state"] is None and (
|
return event_data["old_state"] is None and (
|
||||||
MATCH_ALL in callbacks
|
MATCH_ALL in callbacks
|
||||||
or split_entity_id(event.data["entity_id"])[0] in callbacks
|
or split_entity_id(event_data["entity_id"])[0] in callbacks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -634,12 +634,12 @@ def _async_track_state_added_domain(
|
|||||||
def _async_domain_removed_filter(
|
def _async_domain_removed_filter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]],
|
||||||
event: Event[EventStateChangedData],
|
event_data: EventStateChangedData,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Filter state changes by entity_id."""
|
"""Filter state changes by entity_id."""
|
||||||
return event.data["new_state"] is None and (
|
return event_data["new_state"] is None and (
|
||||||
MATCH_ALL in callbacks
|
MATCH_ALL in callbacks
|
||||||
or split_entity_id(event.data["entity_id"])[0] in callbacks
|
or split_entity_id(event_data["entity_id"])[0] in callbacks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -492,11 +492,11 @@ def async_setup(hass: HomeAssistant) -> None:
|
|||||||
hass.data[TRANSLATION_FLATTEN_CACHE] = cache
|
hass.data[TRANSLATION_FLATTEN_CACHE] = cache
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_load_translations_filter(event: Event) -> bool:
|
def _async_load_translations_filter(event_data: Mapping[str, Any]) -> bool:
|
||||||
"""Filter out unwanted events."""
|
"""Filter out unwanted events."""
|
||||||
nonlocal current_language
|
nonlocal current_language
|
||||||
if (
|
if (
|
||||||
new_language := event.data.get("language")
|
new_language := event_data.get("language")
|
||||||
) and new_language != current_language:
|
) and new_language != current_language:
|
||||||
current_language = new_language
|
current_language = new_language
|
||||||
return True
|
return True
|
||||||
|
@ -97,7 +97,7 @@ async def fire_events_with_filter(hass):
|
|||||||
events_to_fire = 10**6
|
events_to_fire = 10**6
|
||||||
|
|
||||||
@core.callback
|
@core.callback
|
||||||
def event_filter(event):
|
def event_filter(event_data):
|
||||||
"""Filter event."""
|
"""Filter event."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -603,9 +603,9 @@ def _async_when_setup(
|
|||||||
await when_setup()
|
await when_setup()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_is_component_filter(event: Event[EventComponentLoaded]) -> bool:
|
def _async_is_component_filter(event_data: EventComponentLoaded) -> bool:
|
||||||
"""Check if the event is for the component."""
|
"""Check if the event is for the component."""
|
||||||
return event.data[ATTR_COMPONENT] == component
|
return event_data[ATTR_COMPONENT] == component
|
||||||
|
|
||||||
listeners.append(
|
listeners.append(
|
||||||
hass.bus.async_listen(
|
hass.bus.async_listen(
|
||||||
|
@ -98,7 +98,7 @@ def test_repr() -> None:
|
|||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
{"entity_id": "sensor.temperature", "old_state": None, "new_state": state},
|
{"entity_id": "sensor.temperature", "old_state": None, "new_state": state},
|
||||||
context=state.context,
|
context=state.context,
|
||||||
time_fired=fixed_time,
|
time_fired_timestamp=fixed_time.timestamp(),
|
||||||
)
|
)
|
||||||
assert "2016-07-09 11:00:00+00:00" in repr(States.from_event(event))
|
assert "2016-07-09 11:00:00+00:00" in repr(States.from_event(event))
|
||||||
assert "2016-07-09 11:00:00+00:00" in repr(Events.from_event(event))
|
assert "2016-07-09 11:00:00+00:00" in repr(Events.from_event(event))
|
||||||
@ -164,7 +164,7 @@ def test_from_event_to_delete_state() -> None:
|
|||||||
assert db_state.entity_id == "sensor.temperature"
|
assert db_state.entity_id == "sensor.temperature"
|
||||||
assert db_state.state == ""
|
assert db_state.state == ""
|
||||||
assert db_state.last_changed_ts is None
|
assert db_state.last_changed_ts is None
|
||||||
assert db_state.last_updated_ts == event.time_fired.timestamp()
|
assert db_state.last_updated_ts == pytest.approx(event.time_fired.timestamp())
|
||||||
|
|
||||||
|
|
||||||
def test_states_from_native_invalid_entity_id() -> None:
|
def test_states_from_native_invalid_entity_id() -> None:
|
||||||
@ -247,7 +247,10 @@ async def test_process_timestamp_to_utc_isoformat() -> None:
|
|||||||
async def test_event_to_db_model() -> None:
|
async def test_event_to_db_model() -> None:
|
||||||
"""Test we can round trip Event conversion."""
|
"""Test we can round trip Event conversion."""
|
||||||
event = ha.Event(
|
event = ha.Event(
|
||||||
"state_changed", {"some": "attr"}, ha.EventOrigin.local, dt_util.utcnow()
|
"state_changed",
|
||||||
|
{"some": "attr"},
|
||||||
|
ha.EventOrigin.local,
|
||||||
|
dt_util.utcnow().timestamp(),
|
||||||
)
|
)
|
||||||
db_event = Events.from_event(event)
|
db_event = Events.from_event(event)
|
||||||
dialect = SupportedDialect.MYSQL
|
dialect = SupportedDialect.MYSQL
|
||||||
|
@ -78,13 +78,13 @@ async def test_migrate_times(caplog: pytest.LogCaptureFixture, tmp_path: Path) -
|
|||||||
"new_state": mock_state,
|
"new_state": mock_state,
|
||||||
},
|
},
|
||||||
EventOrigin.local,
|
EventOrigin.local,
|
||||||
time_fired=now,
|
time_fired_timestamp=now.timestamp(),
|
||||||
)
|
)
|
||||||
custom_event = Event(
|
custom_event = Event(
|
||||||
"custom_event",
|
"custom_event",
|
||||||
{"entity_id": "sensor.custom"},
|
{"entity_id": "sensor.custom"},
|
||||||
EventOrigin.local,
|
EventOrigin.local,
|
||||||
time_fired=now,
|
time_fired_timestamp=now.timestamp(),
|
||||||
)
|
)
|
||||||
number_of_migrations = 5
|
number_of_migrations = 5
|
||||||
|
|
||||||
@ -242,13 +242,13 @@ async def test_migrate_can_resume_entity_id_post_migration(
|
|||||||
"new_state": mock_state,
|
"new_state": mock_state,
|
||||||
},
|
},
|
||||||
EventOrigin.local,
|
EventOrigin.local,
|
||||||
time_fired=now,
|
time_fired_timestamp=now.timestamp(),
|
||||||
)
|
)
|
||||||
custom_event = Event(
|
custom_event = Event(
|
||||||
"custom_event",
|
"custom_event",
|
||||||
{"entity_id": "sensor.custom"},
|
{"entity_id": "sensor.custom"},
|
||||||
EventOrigin.local,
|
EventOrigin.local,
|
||||||
time_fired=now,
|
time_fired_timestamp=now.timestamp(),
|
||||||
)
|
)
|
||||||
number_of_migrations = 5
|
number_of_migrations = 5
|
||||||
|
|
||||||
|
@ -836,18 +836,23 @@ def test_event_eq() -> None:
|
|||||||
data = {"some": "attr"}
|
data = {"some": "attr"}
|
||||||
context = ha.Context()
|
context = ha.Context()
|
||||||
event1, event2 = (
|
event1, event2 = (
|
||||||
ha.Event("some_type", data, time_fired=now, context=context) for _ in range(2)
|
ha.Event(
|
||||||
|
"some_type", data, time_fired_timestamp=now.timestamp(), context=context
|
||||||
|
)
|
||||||
|
for _ in range(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert event1.as_dict() == event2.as_dict()
|
assert event1.as_dict() == event2.as_dict()
|
||||||
|
|
||||||
|
|
||||||
def test_event_time_fired_timestamp() -> None:
|
def test_event_time() -> None:
|
||||||
"""Test time_fired_timestamp."""
|
"""Test time_fired and time_fired_timestamp."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
event = ha.Event("some_type", {"some": "attr"}, time_fired=now)
|
event = ha.Event(
|
||||||
assert event.time_fired_timestamp == now.timestamp()
|
"some_type", {"some": "attr"}, time_fired_timestamp=now.timestamp()
|
||||||
|
)
|
||||||
assert event.time_fired_timestamp == now.timestamp()
|
assert event.time_fired_timestamp == now.timestamp()
|
||||||
|
assert event.time_fired == now
|
||||||
|
|
||||||
|
|
||||||
def test_event_json_fragment() -> None:
|
def test_event_json_fragment() -> None:
|
||||||
@ -856,7 +861,10 @@ def test_event_json_fragment() -> None:
|
|||||||
data = {"some": "attr"}
|
data = {"some": "attr"}
|
||||||
context = ha.Context()
|
context = ha.Context()
|
||||||
event1, event2 = (
|
event1, event2 = (
|
||||||
ha.Event("some_type", data, time_fired=now, context=context) for _ in range(2)
|
ha.Event(
|
||||||
|
"some_type", data, time_fired_timestamp=now.timestamp(), context=context
|
||||||
|
)
|
||||||
|
for _ in range(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
# We are testing that the JSON fragments are the same when as_dict is called
|
# We are testing that the JSON fragments are the same when as_dict is called
|
||||||
@ -898,7 +906,7 @@ def test_event_as_dict() -> None:
|
|||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
data = {"some": "attr"}
|
data = {"some": "attr"}
|
||||||
|
|
||||||
event = ha.Event(event_type, data, ha.EventOrigin.local, now)
|
event = ha.Event(event_type, data, ha.EventOrigin.local, now.timestamp())
|
||||||
expected = {
|
expected = {
|
||||||
"event_type": event_type,
|
"event_type": event_type,
|
||||||
"data": data,
|
"data": data,
|
||||||
@ -1108,9 +1116,9 @@ async def test_eventbus_filtered_listener(hass: HomeAssistant) -> None:
|
|||||||
calls.append(event)
|
calls.append(event)
|
||||||
|
|
||||||
@ha.callback
|
@ha.callback
|
||||||
def filter(event):
|
def filter(event_data):
|
||||||
"""Mock filter."""
|
"""Mock filter."""
|
||||||
return not event.data["filtered"]
|
return not event_data["filtered"]
|
||||||
|
|
||||||
unsub = hass.bus.async_listen("test", listener, event_filter=filter)
|
unsub = hass.bus.async_listen("test", listener, event_filter=filter)
|
||||||
|
|
||||||
@ -3152,3 +3160,63 @@ async def test_async_add_job_deprecated(
|
|||||||
"https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job"
|
"https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job"
|
||||||
" for replacement options"
|
" for replacement options"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_eventbus_lazy_object_creation(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we don't create unneeded objects when firing events."""
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def listener(event):
|
||||||
|
"""Mock listener."""
|
||||||
|
calls.append(event)
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def filter(event_data):
|
||||||
|
"""Mock filter."""
|
||||||
|
return not event_data["filtered"]
|
||||||
|
|
||||||
|
unsub = hass.bus.async_listen("test_1", listener, event_filter=filter)
|
||||||
|
|
||||||
|
# Test lazy creation of Event objects
|
||||||
|
with patch("homeassistant.core.Event") as mock_event:
|
||||||
|
# Fire an event which is filtered out by its listener
|
||||||
|
hass.bus.async_fire("test_1", {"filtered": True})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_event.assert_not_called()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
# Fire an event which has no listener
|
||||||
|
hass.bus.async_fire("test_2")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_event.assert_not_called()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
# Fire an event which is not filtered out by its listener
|
||||||
|
hass.bus.async_fire("test_1", {"filtered": False})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_event.assert_called_once()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
# Test lazy creation of Context objects
|
||||||
|
with patch("homeassistant.core.Context") as mock_context:
|
||||||
|
# Fire an event which is filtered out by its listener
|
||||||
|
hass.bus.async_fire("test_1", {"filtered": True})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_context.assert_not_called()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
# Fire an event which has no listener
|
||||||
|
hass.bus.async_fire("test_2")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_context.assert_not_called()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
# Fire an event which is not filtered out by its listener
|
||||||
|
hass.bus.async_fire("test_1", {"filtered": False})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_context.assert_called_once()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
unsub()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user