Add created_at/modified_at for area registry (#122014)
parent
054242ff0f
commit
35f84f32d6
|
@ -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()
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue