Bump device registry version to 1.2 (#60199)

This commit is contained in:
Erik Montnemery 2021-11-23 22:22:15 +01:00 committed by GitHub
parent 73d4445f80
commit 24779dea3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 29 deletions

View file

@ -11,6 +11,7 @@ import attr
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import RequiredParameterMissing from homeassistant.exceptions import RequiredParameterMissing
from homeassistant.helpers import storage
from homeassistant.helpers.frame import report from homeassistant.helpers.frame import report
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.enum import StrEnum from homeassistant.util.enum import StrEnum
@ -31,7 +32,8 @@ _LOGGER = logging.getLogger(__name__)
DATA_REGISTRY = "device_registry" DATA_REGISTRY = "device_registry"
EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated" EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated"
STORAGE_KEY = "core.device_registry" STORAGE_KEY = "core.device_registry"
STORAGE_VERSION = 1 STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 2
SAVE_DELAY = 10 SAVE_DELAY = 10
CLEANUP_DELAY = 10 CLEANUP_DELAY = 10
@ -159,6 +161,41 @@ def _async_get_device_id_from_index(
return None return None
class DeviceRegistryStore(storage.Store):
"""Store entity registry data."""
async def _async_migrate_func(
self, old_major_version: int, old_minor_version: int, old_data: dict[str, Any]
) -> dict[str, Any]:
"""Migrate to the new version."""
if old_major_version < 2 and old_minor_version < 2:
# From version 1.1
for device in old_data["devices"]:
# Introduced in 0.110
device["entry_type"] = device.get("entry_type")
# Introduced in 0.79
# renamed in 0.95
device["via_device_id"] = device.get("via_device_id") or device.get(
"hub_device_id"
)
# Introduced in 0.87
device["area_id"] = device.get("area_id")
device["name_by_user"] = device.get("name_by_user")
# Introduced in 0.119
device["disabled_by"] = device.get("disabled_by")
# Introduced in 2021.11
device["configuration_url"] = device.get("configuration_url")
# Introduced in 0.111
old_data["deleted_devices"] = old_data.get("deleted_devices", [])
for device in old_data["deleted_devices"]:
# Introduced in 2021.2
device["orphaned_timestamp"] = device.get("orphaned_timestamp")
if old_major_version > 1:
raise NotImplementedError
return old_data
class DeviceRegistry: class DeviceRegistry:
"""Class to hold a registry of devices.""" """Class to hold a registry of devices."""
@ -170,8 +207,12 @@ class DeviceRegistry:
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the device registry.""" """Initialize the device registry."""
self.hass = hass self.hass = hass
self._store = hass.helpers.storage.Store( self._store = DeviceRegistryStore(
STORAGE_VERSION, STORAGE_KEY, atomic_writes=True hass,
STORAGE_VERSION_MAJOR,
STORAGE_KEY,
atomic_writes=True,
minor_version=STORAGE_VERSION_MINOR,
) )
self._clear_index() self._clear_index()
@ -519,44 +560,36 @@ class DeviceRegistry:
deleted_devices = OrderedDict() deleted_devices = OrderedDict()
if data is not None: if data is not None:
data = cast("dict[str, Any]", data)
for device in data["devices"]: for device in data["devices"]:
devices[device["id"]] = DeviceEntry( devices[device["id"]] = DeviceEntry(
area_id=device["area_id"],
config_entries=set(device["config_entries"]), config_entries=set(device["config_entries"]),
configuration_url=device["configuration_url"],
# type ignores (if tuple arg was cast): likely https://github.com/python/mypy/issues/8625 # type ignores (if tuple arg was cast): likely https://github.com/python/mypy/issues/8625
connections={tuple(conn) for conn in device["connections"]}, # type: ignore[misc] connections={tuple(conn) for conn in device["connections"]}, # type: ignore[misc]
disabled_by=device["disabled_by"],
entry_type=DeviceEntryType(device["entry_type"])
if device["entry_type"]
else None,
id=device["id"],
identifiers={tuple(iden) for iden in device["identifiers"]}, # type: ignore[misc] identifiers={tuple(iden) for iden in device["identifiers"]}, # type: ignore[misc]
manufacturer=device["manufacturer"], manufacturer=device["manufacturer"],
model=device["model"], model=device["model"],
name_by_user=device["name_by_user"],
name=device["name"], name=device["name"],
sw_version=device["sw_version"], sw_version=device["sw_version"],
# Introduced in 0.110 via_device_id=device["via_device_id"],
entry_type=DeviceEntryType(device["entry_type"])
if device.get("entry_type")
else None,
id=device["id"],
# Introduced in 0.79
# renamed in 0.95
via_device_id=(
device.get("via_device_id") or device.get("hub_device_id")
),
# Introduced in 0.87
area_id=device.get("area_id"),
name_by_user=device.get("name_by_user"),
# Introduced in 0.119
disabled_by=device.get("disabled_by"),
# Introduced in 2021.11
configuration_url=device.get("configuration_url"),
) )
# Introduced in 0.111 # Introduced in 0.111
for device in data.get("deleted_devices", []): for device in data["deleted_devices"]:
deleted_devices[device["id"]] = DeletedDeviceEntry( deleted_devices[device["id"]] = DeletedDeviceEntry(
config_entries=set(device["config_entries"]), config_entries=set(device["config_entries"]),
# type ignores (if tuple arg was cast): likely https://github.com/python/mypy/issues/8625 # type ignores (if tuple arg was cast): likely https://github.com/python/mypy/issues/8625
connections={tuple(conn) for conn in device["connections"]}, # type: ignore[misc] connections={tuple(conn) for conn in device["connections"]}, # type: ignore[misc]
identifiers={tuple(iden) for iden in device["identifiers"]}, # type: ignore[misc] identifiers={tuple(iden) for iden in device["identifiers"]}, # type: ignore[misc]
id=device["id"], id=device["id"],
# Introduced in 2021.2 orphaned_timestamp=device["orphaned_timestamp"],
orphaned_timestamp=device.get("orphaned_timestamp"),
) )
self.devices = devices self.devices = devices

View file

@ -167,23 +167,25 @@ async def test_multiple_config_entries(registry):
async def test_loading_from_storage(hass, hass_storage): async def test_loading_from_storage(hass, hass_storage):
"""Test loading stored devices on start.""" """Test loading stored devices on start."""
hass_storage[device_registry.STORAGE_KEY] = { hass_storage[device_registry.STORAGE_KEY] = {
"version": device_registry.STORAGE_VERSION, "version": device_registry.STORAGE_VERSION_MAJOR,
"minor_version": device_registry.STORAGE_VERSION_MINOR,
"data": { "data": {
"devices": [ "devices": [
{ {
"area_id": "12345A",
"config_entries": ["1234"], "config_entries": ["1234"],
"configuration_url": None,
"connections": [["Zigbee", "01.23.45.67.89"]], "connections": [["Zigbee", "01.23.45.67.89"]],
"disabled_by": device_registry.DISABLED_USER,
"entry_type": device_registry.DeviceEntryType.SERVICE,
"id": "abcdefghijklm", "id": "abcdefghijklm",
"identifiers": [["serial", "12:34:56:AB:CD:EF"]], "identifiers": [["serial", "12:34:56:AB:CD:EF"]],
"manufacturer": "manufacturer", "manufacturer": "manufacturer",
"model": "model", "model": "model",
"name_by_user": "Test Friendly Name",
"name": "name", "name": "name",
"sw_version": "version", "sw_version": "version",
"entry_type": device_registry.DeviceEntryType.SERVICE, "via_device_id": None,
"area_id": "12345A",
"name_by_user": "Test Friendly Name",
"disabled_by": device_registry.DISABLED_USER,
"suggested_area": "Kitchen",
} }
], ],
"deleted_devices": [ "deleted_devices": [
@ -192,6 +194,7 @@ async def test_loading_from_storage(hass, hass_storage):
"connections": [["Zigbee", "23.45.67.89.01"]], "connections": [["Zigbee", "23.45.67.89.01"]],
"id": "bcdefghijklmn", "id": "bcdefghijklmn",
"identifiers": [["serial", "34:56:AB:CD:EF:12"]], "identifiers": [["serial", "34:56:AB:CD:EF:12"]],
"orphaned_timestamp": None,
} }
], ],
}, },
@ -231,6 +234,79 @@ async def test_loading_from_storage(hass, hass_storage):
assert isinstance(entry.identifiers, set) assert isinstance(entry.identifiers, set)
@pytest.mark.parametrize("load_registries", [False])
async def test_migration_1_1_to_1_2(hass, hass_storage):
"""Test migration from version 1.1 to 1.2."""
hass_storage[device_registry.STORAGE_KEY] = {
"version": 1,
"minor_version": 1,
"data": {
"devices": [
{
"config_entries": ["1234"],
"connections": [["Zigbee", "01.23.45.67.89"]],
"entry_type": "service",
"id": "abcdefghijklm",
"identifiers": [["serial", "12:34:56:AB:CD:EF"]],
"manufacturer": "manufacturer",
"model": "model",
"name": "name",
"sw_version": "version",
}
],
},
}
await device_registry.async_load(hass)
registry = device_registry.async_get(hass)
# Test data was loaded
entry = registry.async_get_or_create(
config_entry_id="1234",
connections={("Zigbee", "01.23.45.67.89")},
identifiers={("serial", "12:34:56:AB:CD:EF")},
)
assert entry.id == "abcdefghijklm"
# Update to trigger a store
entry = registry.async_get_or_create(
config_entry_id="1234",
connections={("Zigbee", "01.23.45.67.89")},
identifiers={("serial", "12:34:56:AB:CD:EF")},
sw_version="new_version",
)
assert entry.id == "abcdefghijklm"
# Check we store migrated data
await flush_store(registry._store)
assert hass_storage[device_registry.STORAGE_KEY] == {
"version": device_registry.STORAGE_VERSION_MAJOR,
"minor_version": device_registry.STORAGE_VERSION_MINOR,
"key": device_registry.STORAGE_KEY,
"data": {
"devices": [
{
"area_id": None,
"config_entries": ["1234"],
"configuration_url": None,
"connections": [["Zigbee", "01.23.45.67.89"]],
"disabled_by": None,
"entry_type": "service",
"id": "abcdefghijklm",
"identifiers": [["serial", "12:34:56:AB:CD:EF"]],
"manufacturer": "manufacturer",
"model": "model",
"name": "name",
"name_by_user": None,
"sw_version": "new_version",
"via_device_id": None,
}
],
"deleted_devices": [],
},
}
async def test_removing_config_entries(hass, registry, update_events): async def test_removing_config_entries(hass, registry, update_events):
"""Make sure we do not get duplicate entries.""" """Make sure we do not get duplicate entries."""
entry = registry.async_get_or_create( entry = registry.async_get_or_create(