mirror of
https://github.com/home-assistant/core.git
synced 2025-11-13 04:50:17 +00:00
Restore user customizations of deleted devices (#145191)
* Restore user customizations of deleted devices * Apply suggestions from code review * Improve test coverage * Always restore disabled_by
This commit is contained in:
@@ -56,7 +56,7 @@ EVENT_DEVICE_REGISTRY_UPDATED: EventType[EventDeviceRegistryUpdatedData] = Event
|
||||
)
|
||||
STORAGE_KEY = "core.device_registry"
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 9
|
||||
STORAGE_VERSION_MINOR = 10
|
||||
|
||||
CLEANUP_DELAY = 10
|
||||
|
||||
@@ -394,13 +394,17 @@ class DeviceEntry:
|
||||
class DeletedDeviceEntry:
|
||||
"""Deleted Device Registry Entry."""
|
||||
|
||||
area_id: str | None = attr.ib()
|
||||
config_entries: set[str] = attr.ib()
|
||||
config_entries_subentries: dict[str, set[str | None]] = attr.ib()
|
||||
connections: set[tuple[str, str]] = attr.ib()
|
||||
created_at: datetime = attr.ib()
|
||||
disabled_by: DeviceEntryDisabler | None = attr.ib()
|
||||
id: str = attr.ib()
|
||||
identifiers: set[tuple[str, str]] = attr.ib()
|
||||
labels: set[str] = attr.ib()
|
||||
modified_at: datetime = attr.ib()
|
||||
name_by_user: str | None = attr.ib()
|
||||
orphaned_timestamp: float | None = attr.ib()
|
||||
_cache: dict[str, Any] = attr.ib(factory=dict, eq=False, init=False)
|
||||
|
||||
@@ -413,14 +417,18 @@ class DeletedDeviceEntry:
|
||||
) -> DeviceEntry:
|
||||
"""Create DeviceEntry from DeletedDeviceEntry."""
|
||||
return DeviceEntry(
|
||||
area_id=self.area_id,
|
||||
# type ignores: likely https://github.com/python/mypy/issues/8625
|
||||
config_entries={config_entry_id}, # type: ignore[arg-type]
|
||||
config_entries_subentries={config_entry_id: {config_subentry_id}},
|
||||
connections=self.connections & connections, # type: ignore[arg-type]
|
||||
created_at=self.created_at,
|
||||
disabled_by=self.disabled_by,
|
||||
identifiers=self.identifiers & identifiers, # type: ignore[arg-type]
|
||||
id=self.id,
|
||||
is_new=True,
|
||||
labels=self.labels, # type: ignore[arg-type]
|
||||
name_by_user=self.name_by_user,
|
||||
)
|
||||
|
||||
@under_cached_property
|
||||
@@ -429,6 +437,7 @@ class DeletedDeviceEntry:
|
||||
return json_fragment(
|
||||
json_bytes(
|
||||
{
|
||||
"area_id": self.area_id,
|
||||
# The config_entries list can be removed from the storage
|
||||
# representation in HA Core 2026.2
|
||||
"config_entries": list(self.config_entries),
|
||||
@@ -438,9 +447,12 @@ class DeletedDeviceEntry:
|
||||
},
|
||||
"connections": list(self.connections),
|
||||
"created_at": self.created_at,
|
||||
"disabled_by": self.disabled_by,
|
||||
"identifiers": list(self.identifiers),
|
||||
"id": self.id,
|
||||
"labels": list(self.labels),
|
||||
"modified_at": self.modified_at,
|
||||
"name_by_user": self.name_by_user,
|
||||
"orphaned_timestamp": self.orphaned_timestamp,
|
||||
}
|
||||
)
|
||||
@@ -540,6 +552,13 @@ class DeviceRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
|
||||
config_entry_id: {None}
|
||||
for config_entry_id in device["config_entries"]
|
||||
}
|
||||
if old_minor_version < 10:
|
||||
# Introduced in 2025.6
|
||||
for device in old_data["deleted_devices"]:
|
||||
device["area_id"] = None
|
||||
device["disabled_by"] = None
|
||||
device["labels"] = []
|
||||
device["name_by_user"] = None
|
||||
|
||||
if old_major_version > 2:
|
||||
raise NotImplementedError
|
||||
@@ -1238,13 +1257,17 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
self.hass.verify_event_loop_thread("device_registry.async_remove_device")
|
||||
device = self.devices.pop(device_id)
|
||||
self.deleted_devices[device_id] = DeletedDeviceEntry(
|
||||
area_id=device.area_id,
|
||||
config_entries=device.config_entries,
|
||||
config_entries_subentries=device.config_entries_subentries,
|
||||
connections=device.connections,
|
||||
created_at=device.created_at,
|
||||
disabled_by=device.disabled_by,
|
||||
identifiers=device.identifiers,
|
||||
id=device.id,
|
||||
labels=device.labels,
|
||||
modified_at=utcnow(),
|
||||
name_by_user=device.name_by_user,
|
||||
orphaned_timestamp=None,
|
||||
)
|
||||
for other_device in list(self.devices.values()):
|
||||
@@ -1316,6 +1339,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
# Introduced in 0.111
|
||||
for device in data["deleted_devices"]:
|
||||
deleted_devices[device["id"]] = DeletedDeviceEntry(
|
||||
area_id=device["area_id"],
|
||||
config_entries=set(device["config_entries"]),
|
||||
config_entries_subentries={
|
||||
config_entry_id: set(subentries)
|
||||
@@ -1325,9 +1349,16 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
},
|
||||
connections={tuple(conn) for conn in device["connections"]},
|
||||
created_at=datetime.fromisoformat(device["created_at"]),
|
||||
disabled_by=(
|
||||
DeviceEntryDisabler(device["disabled_by"])
|
||||
if device["disabled_by"]
|
||||
else None
|
||||
),
|
||||
identifiers={tuple(iden) for iden in device["identifiers"]},
|
||||
id=device["id"],
|
||||
labels=set(device["labels"]),
|
||||
modified_at=datetime.fromisoformat(device["modified_at"]),
|
||||
name_by_user=device["name_by_user"],
|
||||
orphaned_timestamp=device["orphaned_timestamp"],
|
||||
)
|
||||
|
||||
@@ -1448,12 +1479,26 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
"""Clear area id from registry entries."""
|
||||
for device in self.devices.get_devices_for_area_id(area_id):
|
||||
self.async_update_device(device.id, area_id=None)
|
||||
for deleted_device in list(self.deleted_devices.values()):
|
||||
if deleted_device.area_id != area_id:
|
||||
continue
|
||||
self.deleted_devices[deleted_device.id] = attr.evolve(
|
||||
deleted_device, area_id=None
|
||||
)
|
||||
self.async_schedule_save()
|
||||
|
||||
@callback
|
||||
def async_clear_label_id(self, label_id: str) -> None:
|
||||
"""Clear label from registry entries."""
|
||||
for device in self.devices.get_devices_for_label(label_id):
|
||||
self.async_update_device(device.id, labels=device.labels - {label_id})
|
||||
for deleted_device in list(self.deleted_devices.values()):
|
||||
if label_id not in deleted_device.labels:
|
||||
continue
|
||||
self.deleted_devices[deleted_device.id] = attr.evolve(
|
||||
deleted_device, labels=deleted_device.labels - {label_id}
|
||||
)
|
||||
self.async_schedule_save()
|
||||
|
||||
|
||||
@callback
|
||||
|
||||
Reference in New Issue
Block a user