Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid duplicate timestamp conversions for websocket api and recorder #108144

Merged
merged 4 commits into from Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 2 additions & 6 deletions homeassistant/components/history/websocket_api.py
Expand Up @@ -302,13 +302,9 @@ def _history_compressed_state(state: State, no_attributes: bool) -> dict[str, An
comp_state: dict[str, Any] = {COMPRESSED_STATE_STATE: state.state}
if not no_attributes or state.domain in history.NEED_ATTRIBUTE_DOMAINS:
comp_state[COMPRESSED_STATE_ATTRIBUTES] = state.attributes
comp_state[COMPRESSED_STATE_LAST_UPDATED] = dt_util.utc_to_timestamp(
state.last_updated
)
comp_state[COMPRESSED_STATE_LAST_UPDATED] = state.last_updated_timestamp
if state.last_changed != state.last_updated:
comp_state[COMPRESSED_STATE_LAST_CHANGED] = dt_util.utc_to_timestamp(
state.last_changed
)
comp_state[COMPRESSED_STATE_LAST_CHANGED] = state.last_changed_timestamp
return comp_state


Expand Down
5 changes: 2 additions & 3 deletions homeassistant/components/logbook/models.py
Expand Up @@ -16,7 +16,6 @@
)
from homeassistant.const import ATTR_ICON, EVENT_STATE_CHANGED
from homeassistant.core import Context, Event, State, callback
import homeassistant.util.dt as dt_util
from homeassistant.util.json import json_loads
from homeassistant.util.ulid import ulid_to_bytes

Expand Down Expand Up @@ -131,7 +130,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
context_id_bin=ulid_to_bytes(context.id),
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
time_fired_ts=dt_util.utc_to_timestamp(event.time_fired),
time_fired_ts=event.time_fired_timestamp,
row_id=hash(event),
)
# States are prefiltered so we never get states
Expand All @@ -147,7 +146,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
context_id_bin=ulid_to_bytes(context.id),
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
time_fired_ts=dt_util.utc_to_timestamp(new_state.last_updated),
time_fired_ts=new_state.last_updated_timestamp,
row_id=hash(event),
icon=new_state.attributes.get(ATTR_ICON),
)
8 changes: 4 additions & 4 deletions homeassistant/components/recorder/db_schema.py
Expand Up @@ -296,7 +296,7 @@ def from_event(event: Event) -> Events:
event_data=None,
origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin),
time_fired=None,
time_fired_ts=dt_util.utc_to_timestamp(event.time_fired),
time_fired_ts=event.time_fired_timestamp,
context_id=None,
context_id_bin=ulid_to_bytes_or_none(event.context.id),
context_user_id=None,
Expand Down Expand Up @@ -495,16 +495,16 @@ def from_event(event: Event) -> States:
# None state means the state was removed from the state machine
if state is None:
dbstate.state = ""
dbstate.last_updated_ts = dt_util.utc_to_timestamp(event.time_fired)
dbstate.last_updated_ts = event.time_fired_timestamp
dbstate.last_changed_ts = None
return dbstate

dbstate.state = state.state
dbstate.last_updated_ts = dt_util.utc_to_timestamp(state.last_updated)
dbstate.last_updated_ts = state.last_updated_timestamp
if state.last_updated == state.last_changed:
dbstate.last_changed_ts = None
else:
dbstate.last_changed_ts = dt_util.utc_to_timestamp(state.last_changed)
dbstate.last_changed_ts = state.last_changed_timestamp

return dbstate

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/websocket_api/messages.py
Expand Up @@ -183,9 +183,9 @@ def _state_diff(
if old_state.state != new_state.state:
additions[COMPRESSED_STATE_STATE] = new_state.state
if old_state.last_changed != new_state.last_changed:
additions[COMPRESSED_STATE_LAST_CHANGED] = new_state.last_changed.timestamp()
additions[COMPRESSED_STATE_LAST_CHANGED] = new_state.last_changed_timestamp
elif old_state.last_updated != new_state.last_updated:
additions[COMPRESSED_STATE_LAST_UPDATED] = new_state.last_updated.timestamp()
additions[COMPRESSED_STATE_LAST_UPDATED] = new_state.last_updated_timestamp
if old_state_context.parent_id != new_state_context.parent_id:
additions[COMPRESSED_STATE_CONTEXT] = {"parent_id": new_state_context.parent_id}
if old_state_context.user_id != new_state_context.user_id:
Expand Down
23 changes: 19 additions & 4 deletions homeassistant/core.py
Expand Up @@ -1077,6 +1077,11 @@ def __init__(
if not context.origin_event:
context.origin_event = self

@cached_property
def time_fired_timestamp(self) -> float:
"""Return time fired as a timestamp."""
return self.time_fired.timestamp()

@cached_property
def _as_dict(self) -> dict[str, Any]:
"""Create a dict representation of this Event.
Expand Down Expand Up @@ -1445,6 +1450,16 @@ def name(self) -> str:
"_", " "
)

@cached_property
def last_updated_timestamp(self) -> float:
"""Timestamp of last update."""
return self.last_updated.timestamp()

@cached_property
def last_changed_timestamp(self) -> float:
"""Timestamp of last change."""
return self.last_changed.timestamp()

@cached_property
def _as_dict(self) -> dict[str, Any]:
"""Return a dict representation of the State.
Expand Down Expand Up @@ -1526,12 +1541,12 @@ def as_compressed_state(self) -> dict[str, Any]:
COMPRESSED_STATE_STATE: self.state,
COMPRESSED_STATE_ATTRIBUTES: self.attributes,
COMPRESSED_STATE_CONTEXT: context,
COMPRESSED_STATE_LAST_CHANGED: dt_util.utc_to_timestamp(self.last_changed),
COMPRESSED_STATE_LAST_CHANGED: self.last_changed_timestamp,
}
if self.last_changed != self.last_updated:
compressed_state[COMPRESSED_STATE_LAST_UPDATED] = dt_util.utc_to_timestamp(
self.last_updated
)
compressed_state[
COMPRESSED_STATE_LAST_UPDATED
] = self.last_updated_timestamp
return compressed_state

@cached_property
Expand Down
25 changes: 25 additions & 0 deletions tests/test_core.py
Expand Up @@ -625,6 +625,14 @@ def test_event_eq() -> None:
assert event1.as_dict() == event2.as_dict()


def test_event_time_fired_timestamp() -> None:
"""Test time_fired_timestamp."""
now = dt_util.utcnow()
event = ha.Event("some_type", {"some": "attr"}, time_fired=now)
assert event.time_fired_timestamp == now.timestamp()
assert event.time_fired_timestamp == now.timestamp()


def test_event_json_fragment() -> None:
"""Test event JSON fragments."""
now = dt_util.utcnow()
Expand Down Expand Up @@ -2453,6 +2461,23 @@ async def test_state_change_events_context_id_match_state_time(
)


def test_state_timestamps() -> None:
"""Test timestamp functions for State."""
now = dt_util.utcnow()
state = ha.State(
"light.bedroom",
"on",
{"brightness": 100},
last_changed=now,
last_updated=now,
context=ha.Context(id="1234"),
)
assert state.last_changed_timestamp == now.timestamp()
assert state.last_changed_timestamp == now.timestamp()
assert state.last_updated_timestamp == now.timestamp()
assert state.last_updated_timestamp == now.timestamp()


async def test_state_firing_event_matches_context_id_ulid_time(
hass: HomeAssistant,
) -> None:
Expand Down