Add created_at/modified_at for area registry (#122014)

pull/122071/head
Robert Resch 2024-07-17 09:54:06 +02:00 committed by GitHub
parent 054242ff0f
commit 35f84f32d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 93 additions and 11 deletions

View File

@ -5,11 +5,13 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Iterable
import dataclasses
from datetime import datetime
from functools import cached_property
from typing import Any, Literal, TypedDict
from homeassistant.core import HomeAssistant, callback
from homeassistant.util import slugify
from homeassistant.util.dt import utc_from_timestamp, utcnow
from homeassistant.util.event_type import EventType
from homeassistant.util.hass_dict import HassKey
@ -31,7 +33,7 @@ EVENT_AREA_REGISTRY_UPDATED: EventType[EventAreaRegistryUpdatedData] = EventType
)
STORAGE_KEY = "core.area_registry"
STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 6
STORAGE_VERSION_MINOR = 7
class _AreaStoreData(TypedDict):
@ -44,6 +46,8 @@ class _AreaStoreData(TypedDict):
labels: list[str]
name: str
picture: str | None
created_at: datetime
modified_at: datetime
class AreasRegistryStoreData(TypedDict):
@ -83,6 +87,8 @@ class AreaEntry(NormalizedNameBaseRegistryEntry):
"labels": list(self.labels),
"name": self.name,
"picture": self.picture,
"created_at": self.created_at,
"modified_at": self.modified_at,
}
)
)
@ -125,6 +131,11 @@ class AreaRegistryStore(Store[AreasRegistryStoreData]):
for area in old_data["areas"]:
area["labels"] = []
if old_minor_version < 7:
# Version 1.7 adds created_at and modiefied_at
for area in old_data["areas"]:
area["created_at"] = area["modified_at"] = utc_from_timestamp(0)
if old_major_version > 1:
raise NotImplementedError
return old_data # type: ignore[return-value]
@ -315,7 +326,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
"""Update name of area."""
old = self.areas[area_id]
new_values = {
new_values: dict[str, Any] = {
attr_name: value
for attr_name, value in (
("aliases", aliases),
@ -334,8 +345,10 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
if not new_values:
return old
new_values["modified_at"] = utcnow()
self.hass.verify_event_loop_thread("area_registry.async_update")
new = self.areas[area_id] = dataclasses.replace(old, **new_values) # type: ignore[arg-type]
new = self.areas[area_id] = dataclasses.replace(old, **new_values)
self.async_schedule_save()
return new
@ -361,6 +374,8 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
name=area["name"],
normalized_name=normalized_name,
picture=area["picture"],
created_at=area["created_at"],
modified_at=area["modified_at"],
)
self.areas = areas
@ -379,6 +394,8 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
"labels": list(entry.labels),
"name": entry.name,
"picture": entry.picture,
"created_at": entry.created_at,
"modified_at": entry.modified_at,
}
for entry in self.areas.values()
]

View File

@ -1,8 +1,11 @@
"""Provide a base class for registries that use a normalized name index."""
from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import datetime
from functools import lru_cache
from homeassistant.util import dt as dt_util
from .registry import BaseRegistryItems
@ -12,6 +15,8 @@ class NormalizedNameBaseRegistryEntry:
name: str
normalized_name: str
created_at: datetime = field(default_factory=dt_util.utcnow)
modified_at: datetime = field(default_factory=dt_util.utcnow)
@lru_cache(maxsize=1024)

View File

@ -1,11 +1,13 @@
"""Test area_registry API."""
from freezegun.api import FrozenDateTimeFactory
import pytest
from pytest_unordered import unordered
from homeassistant.components.config import area_registry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import area_registry as ar
from homeassistant.util.dt import utcnow
from tests.common import ANY
from tests.typing import MockHAClientWebSocket, WebSocketGenerator
@ -21,10 +23,17 @@ async def client_fixture(
async def test_list_areas(
client: MockHAClientWebSocket, area_registry: ar.AreaRegistry
client: MockHAClientWebSocket,
area_registry: ar.AreaRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test list entries."""
created_area1 = "2024-07-16T13:30:00.900075+00:00"
freezer.move_to(created_area1)
area1 = area_registry.async_create("mock 1")
created_area2 = "2024-07-16T13:45:00.900075+00:00"
freezer.move_to(created_area2)
area2 = area_registry.async_create(
"mock 2",
aliases={"alias_1", "alias_2"},
@ -46,6 +55,8 @@ async def test_list_areas(
"labels": [],
"name": "mock 1",
"picture": None,
"created_at": created_area1,
"modified_at": created_area1,
},
{
"aliases": unordered(["alias_1", "alias_2"]),
@ -55,12 +66,16 @@ async def test_list_areas(
"labels": unordered(["label_1", "label_2"]),
"name": "mock 2",
"picture": "/image/example.png",
"created_at": created_area2,
"modified_at": created_area2,
},
]
async def test_create_area(
client: MockHAClientWebSocket, area_registry: ar.AreaRegistry
client: MockHAClientWebSocket,
area_registry: ar.AreaRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test create entry."""
# Create area with only mandatory parameters
@ -78,6 +93,8 @@ async def test_create_area(
"labels": [],
"name": "mock",
"picture": None,
"created_at": utcnow().isoformat(),
"modified_at": utcnow().isoformat(),
}
assert len(area_registry.areas) == 1
@ -104,6 +121,8 @@ async def test_create_area(
"labels": unordered(["label_1", "label_2"]),
"name": "mock 2",
"picture": "/image/example.png",
"created_at": utcnow().isoformat(),
"modified_at": utcnow().isoformat(),
}
assert len(area_registry.areas) == 2
@ -161,10 +180,16 @@ async def test_delete_non_existing_area(
async def test_update_area(
client: MockHAClientWebSocket, area_registry: ar.AreaRegistry
client: MockHAClientWebSocket,
area_registry: ar.AreaRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test update entry."""
created_at = "2024-07-16T13:30:00.900075+00:00"
freezer.move_to(created_at)
area = area_registry.async_create("mock 1")
modified_at = "2024-07-16T13:45:00.900075+00:00"
freezer.move_to(modified_at)
await client.send_json_auto_id(
{
@ -189,9 +214,14 @@ async def test_update_area(
"labels": unordered(["label_1", "label_2"]),
"name": "mock 2",
"picture": "/image/example.png",
"created_at": created_at,
"modified_at": modified_at,
}
assert len(area_registry.areas) == 1
modified_at = "2024-07-16T13:50:00.900075+00:00"
freezer.move_to(modified_at)
await client.send_json_auto_id(
{
"aliases": ["alias_1", "alias_1"],
@ -214,6 +244,8 @@ async def test_update_area(
"labels": [],
"name": "mock 2",
"picture": None,
"created_at": created_at,
"modified_at": modified_at,
}
assert len(area_registry.areas) == 1

View File

@ -1,8 +1,10 @@
"""Tests for the Area Registry."""
from datetime import datetime, timedelta
from functools import partial
from typing import Any
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.core import HomeAssistant
@ -11,6 +13,7 @@ from homeassistant.helpers import (
floor_registry as fr,
label_registry as lr,
)
from homeassistant.util.dt import utcnow
from tests.common import ANY, async_capture_events, flush_store
@ -24,7 +27,11 @@ async def test_list_areas(area_registry: ar.AreaRegistry) -> None:
assert len(areas) == len(area_registry.areas)
async def test_create_area(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None:
async def test_create_area(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
area_registry: ar.AreaRegistry,
) -> None:
"""Make sure that we can create an area."""
update_events = async_capture_events(hass, ar.EVENT_AREA_REGISTRY_UPDATED)
@ -40,9 +47,13 @@ async def test_create_area(hass: HomeAssistant, area_registry: ar.AreaRegistry)
name="mock",
normalized_name=ANY,
picture=None,
created_at=utcnow(),
modified_at=utcnow(),
)
assert len(area_registry.areas) == 1
freezer.tick(timedelta(minutes=5))
await hass.async_block_till_done()
assert len(update_events) == 1
@ -52,14 +63,14 @@ async def test_create_area(hass: HomeAssistant, area_registry: ar.AreaRegistry)
}
# Create area with all parameters
area = area_registry.async_create(
area2 = area_registry.async_create(
"mock 2",
aliases={"alias_1", "alias_2"},
labels={"label1", "label2"},
picture="/image/example.png",
)
assert area == ar.AreaEntry(
assert area2 == ar.AreaEntry(
aliases={"alias_1", "alias_2"},
floor_id=None,
icon=None,
@ -68,15 +79,19 @@ async def test_create_area(hass: HomeAssistant, area_registry: ar.AreaRegistry)
name="mock 2",
normalized_name=ANY,
picture="/image/example.png",
created_at=utcnow(),
modified_at=utcnow(),
)
assert len(area_registry.areas) == 2
assert area.created_at != area2.created_at
assert area.modified_at != area2.modified_at
await hass.async_block_till_done()
assert len(update_events) == 2
assert update_events[-1].data == {
"action": "create",
"area_id": area.id,
"area_id": area2.id,
}
@ -150,11 +165,18 @@ async def test_update_area(
area_registry: ar.AreaRegistry,
floor_registry: fr.FloorRegistry,
label_registry: lr.LabelRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Make sure that we can read areas."""
created_at = datetime.fromisoformat("2024-01-01T01:00:00+00:00")
freezer.move_to(created_at)
update_events = async_capture_events(hass, ar.EVENT_AREA_REGISTRY_UPDATED)
floor_registry.async_create("first")
area = area_registry.async_create("mock")
assert area.modified_at == created_at
modified_at = datetime.fromisoformat("2024-02-01T01:00:00+00:00")
freezer.move_to(modified_at)
updated_area = area_registry.async_update(
area.id,
@ -176,6 +198,8 @@ async def test_update_area(
name="mock1",
normalized_name=ANY,
picture="/image/example.png",
created_at=created_at,
modified_at=modified_at,
)
assert len(area_registry.areas) == 1
@ -285,6 +309,8 @@ async def test_loading_area_from_storage(
"labels": ["mock-label1", "mock-label2"],
"name": "mock",
"picture": "blah",
"created_at": utcnow().isoformat(),
"modified_at": utcnow().isoformat(),
}
]
},
@ -329,6 +355,8 @@ async def test_migration_from_1_1(
"labels": [],
"name": "mock",
"picture": None,
"created_at": "1970-01-01T00:00:00+00:00",
"modified_at": "1970-01-01T00:00:00+00:00",
}
]
},