From f9205cd88d24ceaf5352e87f577bc6cf0723cf62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Jun 2024 22:43:34 -0500 Subject: [PATCH] Avoid additional timestamp conversion to set state (#118885) Avoid addtional timestamp conversion to set state Since we already have the timestamp, we can pass it on to the State object and avoid the additional timestamp conversion which can be as much as 30% of the state write runtime. Since datetime objects are limited to microsecond precision, we need to adjust some tests to account for the additional precision that we will now be able to get in the database --- homeassistant/core.py | 5 +- .../components/history/test_websocket_api.py | 384 +++++++++++++----- .../components/logbook/test_websocket_api.py | 38 +- .../components/websocket_api/test_messages.py | 20 +- tests/test_core.py | 28 +- 5 files changed, 332 insertions(+), 143 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index d0e80ad8bd1..7aa823dc042 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1762,6 +1762,7 @@ class State: context: Context | None = None, validate_entity_id: bool | None = True, state_info: StateInfo | None = None, + last_updated_timestamp: float | None = None, ) -> None: """Initialize a new state.""" state = str(state) @@ -1793,7 +1794,8 @@ class State: # so we will set the timestamp values here to avoid the overhead of # the function call in the property we know will always be called. last_updated = self.last_updated - last_updated_timestamp = last_updated.timestamp() + if not last_updated_timestamp: + last_updated_timestamp = last_updated.timestamp() self.last_updated_timestamp = last_updated_timestamp if self.last_changed == last_updated: self.__dict__["last_changed_timestamp"] = last_updated_timestamp @@ -2309,6 +2311,7 @@ class StateMachine: context, old_state is None, state_info, + timestamp, ) if old_state is not None: old_state.expire() diff --git a/tests/components/history/test_websocket_api.py b/tests/components/history/test_websocket_api.py index 580853fb83f..e5c33d0e7af 100644 --- a/tests/components/history/test_websocket_api.py +++ b/tests/components/history/test_websocket_api.py @@ -466,16 +466,24 @@ async def test_history_stream_historical_only( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "changed"}) - sensor_three_last_updated = hass.states.get("sensor.three").last_updated + sensor_three_last_updated_timestamp = hass.states.get( + "sensor.three" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.four", "off", attributes={"any": "again"}) - sensor_four_last_updated = hass.states.get("sensor.four").last_updated + sensor_four_last_updated_timestamp = hass.states.get( + "sensor.four" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -506,17 +514,27 @@ async def test_history_stream_historical_only( assert response == { "event": { - "end_time": sensor_four_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_four_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.four": [ - {"lu": sensor_four_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_four_last_updated_timestamp), + "s": "off", + } + ], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} ], - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], "sensor.three": [ - {"lu": sensor_three_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_three_last_updated_timestamp), + "s": "off", + } + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} ], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], }, }, "id": 1, @@ -817,10 +835,14 @@ async def test_history_stream_live_no_attributes_minimal_response( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -846,15 +868,19 @@ async def test_history_stream_live_no_attributes_minimal_response( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -866,14 +892,22 @@ async def test_history_stream_live_no_attributes_minimal_response( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -894,10 +928,14 @@ async def test_history_stream_live( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -923,24 +961,24 @@ async def test_history_stream_live( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ { "a": {"any": "attr"}, - "lu": sensor_one_last_updated.timestamp(), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", } ], "sensor.two": [ { "a": {"any": "attr"}, - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off", } ], @@ -955,24 +993,30 @@ async def test_history_stream_live( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_one_last_changed = hass.states.get("sensor.one").last_changed - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_one_last_changed_timestamp = hass.states.get( + "sensor.one" + ).last_changed_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { "sensor.one": [ { - "lc": sensor_one_last_changed.timestamp(), - "lu": sensor_one_last_updated.timestamp(), + "lc": pytest.approx(sensor_one_last_changed_timestamp), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", "a": {"diff": "attr"}, } ], "sensor.two": [ { - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two", "a": {"any": "attr"}, } @@ -997,10 +1041,14 @@ async def test_history_stream_live_minimal_response( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1026,24 +1074,24 @@ async def test_history_stream_live_minimal_response( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, + "end_time": pytest.approx(first_end_time), "start_time": now.timestamp(), "states": { "sensor.one": [ { "a": {"any": "attr"}, - "lu": sensor_one_last_updated.timestamp(), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", } ], "sensor.two": [ { "a": {"any": "attr"}, - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off", } ], @@ -1057,8 +1105,12 @@ async def test_history_stream_live_minimal_response( hass.states.async_set("sensor.one", "on", attributes={"diff": "attr"}) hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) # Only sensor.two has changed - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp hass.states.async_remove("sensor.one") hass.states.async_remove("sensor.two") await async_recorder_block_till_done(hass) @@ -1069,7 +1121,7 @@ async def test_history_stream_live_minimal_response( "states": { "sensor.two": [ { - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two", "a": {"any": "attr"}, } @@ -1094,10 +1146,14 @@ async def test_history_stream_live_no_attributes( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1123,18 +1179,26 @@ async def test_history_stream_live_no_attributes( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "a": {}, + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, }, @@ -1147,14 +1211,22 @@ async def test_history_stream_live_no_attributes( hass.states.async_set("sensor.two", "two", attributes={"diff": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1176,10 +1248,14 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1205,15 +1281,19 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -1225,14 +1305,22 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1254,10 +1342,14 @@ async def test_history_stream_live_with_future_end_time( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1287,15 +1379,19 @@ async def test_history_stream_live_with_future_end_time( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -1307,14 +1403,22 @@ async def test_history_stream_live_with_future_end_time( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1450,10 +1554,14 @@ async def test_overflow_queue( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1481,18 +1589,24 @@ async def test_overflow_queue( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ - {"lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, }, @@ -1522,10 +1636,14 @@ async def test_history_during_period_for_invalid_entity_ids( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "again"}) await async_recorder_block_till_done(hass) @@ -1550,7 +1668,11 @@ async def test_history_during_period_for_invalid_entity_ids( assert response == { "result": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], }, "id": 1, @@ -1574,10 +1696,18 @@ async def test_history_during_period_for_invalid_entity_ids( assert response == { "result": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "a": {}, + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, "id": 2, @@ -1670,10 +1800,14 @@ async def test_history_stream_for_invalid_entity_ids( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "again"}) await async_recorder_block_till_done(hass) @@ -1703,10 +1837,12 @@ async def test_history_stream_for_invalid_entity_ids( response = await client.receive_json() assert response == { "event": { - "end_time": sensor_one_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_one_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], }, }, "id": 1, @@ -1733,11 +1869,15 @@ async def test_history_stream_for_invalid_entity_ids( response = await client.receive_json() assert response == { "event": { - "end_time": sensor_two_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_two_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 2, @@ -1841,21 +1981,31 @@ async def test_history_stream_historical_only_with_start_time_state_past( now = dt_util.utcnow() await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "second", attributes={"any": "attr"}) - sensor_one_last_updated_second = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_second_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await asyncio.sleep(0.00001) hass.states.async_set("sensor.one", "third", attributes={"any": "attr"}) - sensor_one_last_updated_third = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_third_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "changed"}) - sensor_three_last_updated = hass.states.get("sensor.three").last_updated + sensor_three_last_updated_timestamp = hass.states.get( + "sensor.three" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.four", "off", attributes={"any": "again"}) - sensor_four_last_updated = hass.states.get("sensor.four").last_updated + sensor_four_last_updated_timestamp = hass.states.get( + "sensor.four" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1885,24 +2035,38 @@ async def test_history_stream_historical_only_with_start_time_state_past( assert response == { "event": { - "end_time": sensor_four_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_four_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.four": [ - {"lu": sensor_four_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_four_last_updated_timestamp), + "s": "off", + } ], "sensor.one": [ { - "lu": now.timestamp(), + "lu": pytest.approx(now.timestamp()), "s": "first", }, # should use start time state - {"lu": sensor_one_last_updated_second.timestamp(), "s": "second"}, - {"lu": sensor_one_last_updated_third.timestamp(), "s": "third"}, + { + "lu": pytest.approx(sensor_one_last_updated_second_timestamp), + "s": "second", + }, + { + "lu": pytest.approx(sensor_one_last_updated_third_timestamp), + "s": "third", + }, ], "sensor.three": [ - {"lu": sensor_three_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_three_last_updated_timestamp), + "s": "off", + } + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} ], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], }, }, "id": 1, diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index 1fb0e6eb24b..bd11c87f4df 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -630,7 +630,7 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -679,17 +679,17 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1033,7 +1033,7 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -1082,17 +1082,17 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1201,7 +1201,7 @@ async def test_subscribe_unsubscribe_logbook_stream( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -1241,17 +1241,17 @@ async def test_subscribe_unsubscribe_logbook_stream( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1514,7 +1514,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1613,7 +1613,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_with_end_time( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1716,7 +1716,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_past_only( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1804,7 +1804,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query( { "entity_id": "binary_sensor.is_light", "state": "on", - "when": current_state.last_updated.timestamp(), + "when": current_state.last_updated_timestamp, } ] @@ -1817,7 +1817,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query( { "entity_id": "binary_sensor.four_days_ago", "state": "off", - "when": four_day_old_state.last_updated.timestamp(), + "when": four_day_old_state.last_updated_timestamp, } ] @@ -2363,7 +2363,7 @@ async def test_subscribe_disconnected( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -2790,7 +2790,7 @@ async def test_logbook_stream_ignores_forced_updates( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() diff --git a/tests/components/websocket_api/test_messages.py b/tests/components/websocket_api/test_messages.py index 6294b6a2628..cb8a026fe0d 100644 --- a/tests/components/websocket_api/test_messages.py +++ b/tests/components/websocket_api/test_messages.py @@ -96,9 +96,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: message = _state_diff_event(last_state_event) assert message == { "c": { - "light.window": { - "+": {"lc": new_state.last_changed.timestamp(), "s": "off"} - } + "light.window": {"+": {"lc": new_state.last_changed_timestamp, "s": "off"}} } } @@ -117,7 +115,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": {"parent_id": "new-parent-id"}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "red", } } @@ -144,7 +142,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "parent_id": "another-new-parent-id", "user_id": "new-user-id", }, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "green", } } @@ -168,7 +166,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": {"user_id": "another-new-user-id"}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "blue", } } @@ -194,7 +192,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": "id-new", - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "yellow", } } @@ -216,7 +214,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "+": { "a": {"new": "attr"}, "c": {"id": new_context.id, "parent_id": None, "user_id": None}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "purple", } } @@ -232,7 +230,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: assert message == { "c": { "light.window": { - "+": {"lc": new_state.last_changed.timestamp(), "s": "green"}, + "+": {"lc": new_state.last_changed_timestamp, "s": "green"}, "-": {"a": ["new"]}, } } @@ -254,7 +252,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "a": {"list_attr": ["a", "b", "c", "d"], "list_attr_2": ["a", "b"]}, - "lu": new_state.last_updated.timestamp(), + "lu": new_state.last_updated_timestamp, } } } @@ -275,7 +273,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "a": {"list_attr": ["a", "b", "c", "e"]}, - "lu": new_state.last_updated.timestamp(), + "lu": new_state.last_updated_timestamp, }, "-": {"a": ["list_attr_2"]}, } diff --git a/tests/test_core.py b/tests/test_core.py index fa94b4e658c..6848d209d02 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2834,8 +2834,32 @@ async def test_state_change_events_context_id_match_state_time( assert state.last_updated == events[0].time_fired assert len(state.context.id) == 26 # ULIDs store time to 3 decimal places compared to python timestamps - assert _ulid_timestamp(state.context.id) == int( - state.last_updated.timestamp() * 1000 + assert _ulid_timestamp(state.context.id) == int(state.last_updated_timestamp * 1000) + + +async def test_state_change_events_match_time_with_limits_of_precision( + hass: HomeAssistant, +) -> None: + """Ensure last_updated matches last_updated_timestamp within limits of precision. + + The last_updated_timestamp uses the same precision as time.time() which is + a bit better than the precision of datetime.now() which is used for last_updated + on some platforms. + """ + events = async_capture_events(hass, ha.EVENT_STATE_CHANGED) + hass.states.async_set("light.bedroom", "on") + await hass.async_block_till_done() + state: State = hass.states.get("light.bedroom") + assert state.last_updated == events[0].time_fired + assert state.last_updated_timestamp == pytest.approx( + events[0].time_fired.timestamp() + ) + assert state.last_updated_timestamp == pytest.approx(state.last_updated.timestamp()) + assert state.last_updated_timestamp == state.last_changed_timestamp + assert state.last_updated_timestamp == pytest.approx(state.last_changed.timestamp()) + assert state.last_updated_timestamp == state.last_reported_timestamp + assert state.last_updated_timestamp == pytest.approx( + state.last_reported.timestamp() )