Remove is_new from device entry (#149835)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Artur Pragacz
2025-08-28 08:07:54 +02:00
committed by GitHub
parent f4673f44ee
commit 61328129fc
4 changed files with 87 additions and 31 deletions

View File

@@ -118,7 +118,6 @@ async def async_get_config_entry_diagnostics(
device_dict.pop("_cache", None) device_dict.pop("_cache", None)
# This can be removed when suggested_area is removed from DeviceEntry # This can be removed when suggested_area is removed from DeviceEntry
device_dict.pop("_suggested_area") device_dict.pop("_suggested_area")
device_dict.pop("is_new", None)
device_entities.append({"device": device_dict, "entities": entities}) device_entities.append({"device": device_dict, "entities": entities})
# remove envoy serial # remove envoy serial

View File

@@ -349,8 +349,6 @@ class DeviceEntry:
_suggested_area: str | None = attr.ib(default=None) _suggested_area: str | None = attr.ib(default=None)
sw_version: str | None = attr.ib(default=None) sw_version: str | None = attr.ib(default=None)
via_device_id: str | None = attr.ib(default=None) via_device_id: str | None = attr.ib(default=None)
# This value is not stored, just used to keep track of events to fire.
is_new: bool = attr.ib(default=False)
_cache: dict[str, Any] = attr.ib(factory=dict, eq=False, init=False) _cache: dict[str, Any] = attr.ib(factory=dict, eq=False, init=False)
@property @property
@@ -499,7 +497,6 @@ class DeletedDeviceEntry:
disabled_by=disabled_by, disabled_by=disabled_by,
identifiers=self.identifiers & identifiers, # type: ignore[arg-type] identifiers=self.identifiers & identifiers, # type: ignore[arg-type]
id=self.id, id=self.id,
is_new=True,
labels=self.labels, # type: ignore[arg-type] labels=self.labels, # type: ignore[arg-type]
name_by_user=self.name_by_user, name_by_user=self.name_by_user,
) )
@@ -910,7 +907,11 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
identifiers=identifiers, connections=connections identifiers=identifiers, connections=connections
) )
is_new = False
if device is None: if device is None:
is_new = True
deleted_device = self.deleted_devices.get_entry(identifiers, connections) deleted_device = self.deleted_devices.get_entry(identifiers, connections)
if deleted_device is None: if deleted_device is None:
area_id: str | None = None area_id: str | None = None
@@ -924,7 +925,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
area = ar.async_get(self.hass).async_get_or_create(suggested_area) area = ar.async_get(self.hass).async_get_or_create(suggested_area)
area_id = area.id area_id = area.id
device = DeviceEntry(is_new=True, area_id=area_id) device = DeviceEntry(area_id=area_id)
else: else:
self.deleted_devices.pop(deleted_device.id) self.deleted_devices.pop(deleted_device.id)
@@ -935,6 +936,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
connections, connections,
identifiers, identifiers,
) )
self.devices[device.id] = device self.devices[device.id] = device
# If creating a new device, default to the config entry name # If creating a new device, default to the config entry name
if device_info_type == "primary" and (not name or name is UNDEFINED): if device_info_type == "primary" and (not name or name is UNDEFINED):
@@ -963,7 +965,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
else: else:
via_device_id = UNDEFINED via_device_id = UNDEFINED
device = self.async_update_device( device = self._async_update_device(
device.id, device.id,
allow_collisions=True, allow_collisions=True,
add_config_entry_id=config_entry_id, add_config_entry_id=config_entry_id,
@@ -973,6 +975,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
disabled_by=disabled_by, disabled_by=disabled_by,
entry_type=entry_type, entry_type=entry_type,
hw_version=hw_version, hw_version=hw_version,
is_new=is_new,
manufacturer=manufacturer, manufacturer=manufacturer,
merge_connections=connections or UNDEFINED, merge_connections=connections or UNDEFINED,
merge_identifiers=identifiers or UNDEFINED, merge_identifiers=identifiers or UNDEFINED,
@@ -980,7 +983,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
model_id=model_id, model_id=model_id,
name=name, name=name,
serial_number=serial_number, serial_number=serial_number,
_suggested_area=suggested_area, suggested_area=suggested_area,
sw_version=sw_version, sw_version=sw_version,
via_device_id=via_device_id, via_device_id=via_device_id,
) )
@@ -991,14 +994,14 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
return device return device
@callback @callback
def async_update_device( # noqa: C901 def _async_update_device( # noqa: C901
self, self,
device_id: str, device_id: str,
*, *,
add_config_entry_id: str | UndefinedType = UNDEFINED, add_config_entry_id: str | UndefinedType = UNDEFINED,
add_config_subentry_id: str | None | UndefinedType = UNDEFINED, add_config_subentry_id: str | None | UndefinedType = UNDEFINED,
# Temporary flag so we don't blow up when collisions are implicitly introduced # Temporary flag so we don't blow up when collisions are implicitly introduced
# by calls to async_get_or_create. Must not be set by integrations. # by calls to async_get_or_create.
allow_collisions: bool = False, allow_collisions: bool = False,
area_id: str | None | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED,
configuration_url: str | URL | None | UndefinedType = UNDEFINED, configuration_url: str | URL | None | UndefinedType = UNDEFINED,
@@ -1006,6 +1009,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED, disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED,
entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED, entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED,
hw_version: str | None | UndefinedType = UNDEFINED, hw_version: str | None | UndefinedType = UNDEFINED,
is_new: bool = False,
labels: set[str] | UndefinedType = UNDEFINED, labels: set[str] | UndefinedType = UNDEFINED,
manufacturer: str | None | UndefinedType = UNDEFINED, manufacturer: str | None | UndefinedType = UNDEFINED,
merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED,
@@ -1019,15 +1023,12 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
remove_config_entry_id: str | UndefinedType = UNDEFINED, remove_config_entry_id: str | UndefinedType = UNDEFINED,
remove_config_subentry_id: str | None | UndefinedType = UNDEFINED, remove_config_subentry_id: str | None | UndefinedType = UNDEFINED,
serial_number: str | None | UndefinedType = UNDEFINED, serial_number: str | None | UndefinedType = UNDEFINED,
# _suggested_area is used internally by the device registry and must # Can be removed when suggested_area is removed from DeviceEntry
# not be set by integrations.
_suggested_area: str | None | UndefinedType = UNDEFINED,
# suggested_area is deprecated and will be removed in 2026.9
suggested_area: str | None | UndefinedType = UNDEFINED, suggested_area: str | None | UndefinedType = UNDEFINED,
sw_version: str | None | UndefinedType = UNDEFINED, sw_version: str | None | UndefinedType = UNDEFINED,
via_device_id: str | None | UndefinedType = UNDEFINED, via_device_id: str | None | UndefinedType = UNDEFINED,
) -> DeviceEntry | None: ) -> DeviceEntry | None:
"""Update device attributes. """Private update device attributes.
:param add_config_subentry_id: Add the device to a specific subentry of add_config_entry_id :param add_config_subentry_id: Add the device to a specific subentry of add_config_entry_id
:param remove_config_subentry_id: Remove the device from a specific subentry of remove_config_subentry_id :param remove_config_subentry_id: Remove the device from a specific subentry of remove_config_subentry_id
@@ -1191,16 +1192,6 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
new_values["config_entries_subentries"] = config_entries_subentries new_values["config_entries_subentries"] = config_entries_subentries
old_values["config_entries_subentries"] = old.config_entries_subentries old_values["config_entries_subentries"] = old.config_entries_subentries
if suggested_area is not UNDEFINED:
report_usage(
"passes a suggested_area to device_registry.async_update device",
core_behavior=ReportBehavior.LOG,
breaks_in_ha_version="2026.9.0",
)
if _suggested_area is not UNDEFINED:
suggested_area = _suggested_area
added_connections: set[tuple[str, str]] | None = None added_connections: set[tuple[str, str]] | None = None
added_identifiers: set[tuple[str, str]] | None = None added_identifiers: set[tuple[str, str]] | None = None
@@ -1266,10 +1257,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
new_values["suggested_area"] = suggested_area new_values["suggested_area"] = suggested_area
old_values["suggested_area"] = old._suggested_area # noqa: SLF001 old_values["suggested_area"] = old._suggested_area # noqa: SLF001
if old.is_new: if not new_values and not is_new:
new_values["is_new"] = False
if not new_values:
return old return old
# This condition can be removed when suggested_area is removed from DeviceEntry # This condition can be removed when suggested_area is removed from DeviceEntry
@@ -1301,7 +1289,7 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
self.async_schedule_save() self.async_schedule_save()
data: EventDeviceRegistryUpdatedData data: EventDeviceRegistryUpdatedData
if old.is_new: if is_new:
data = {"action": "create", "device_id": new.id} data = {"action": "create", "device_id": new.id}
else: else:
data = {"action": "update", "device_id": new.id, "changes": old_values} data = {"action": "update", "device_id": new.id, "changes": old_values}
@@ -1310,6 +1298,77 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
return new return new
@callback
def async_update_device(
self,
device_id: str,
*,
add_config_entry_id: str | UndefinedType = UNDEFINED,
add_config_subentry_id: str | None | UndefinedType = UNDEFINED,
area_id: str | None | UndefinedType = UNDEFINED,
configuration_url: str | URL | None | UndefinedType = UNDEFINED,
device_info_type: str | UndefinedType = UNDEFINED,
disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED,
entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED,
hw_version: str | None | UndefinedType = UNDEFINED,
labels: set[str] | UndefinedType = UNDEFINED,
manufacturer: str | None | UndefinedType = UNDEFINED,
merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED,
merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED,
model: str | None | UndefinedType = UNDEFINED,
model_id: str | None | UndefinedType = UNDEFINED,
name_by_user: str | None | UndefinedType = UNDEFINED,
name: str | None | UndefinedType = UNDEFINED,
new_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED,
new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED,
remove_config_entry_id: str | UndefinedType = UNDEFINED,
remove_config_subentry_id: str | None | UndefinedType = UNDEFINED,
serial_number: str | None | UndefinedType = UNDEFINED,
# suggested_area is deprecated and will be removed in 2026.9
suggested_area: str | None | UndefinedType = UNDEFINED,
sw_version: str | None | UndefinedType = UNDEFINED,
via_device_id: str | None | UndefinedType = UNDEFINED,
) -> DeviceEntry | None:
"""Update device attributes.
:param add_config_subentry_id: Add the device to a specific subentry of add_config_entry_id
:param remove_config_subentry_id: Remove the device from a specific subentry of remove_config_subentry_id
"""
if suggested_area is not UNDEFINED:
report_usage(
"passes a suggested_area to device_registry.async_update device",
core_behavior=ReportBehavior.LOG,
breaks_in_ha_version="2026.9.0",
)
return self._async_update_device(
device_id,
add_config_entry_id=add_config_entry_id,
add_config_subentry_id=add_config_subentry_id,
area_id=area_id,
configuration_url=configuration_url,
device_info_type=device_info_type,
disabled_by=disabled_by,
entry_type=entry_type,
hw_version=hw_version,
labels=labels,
manufacturer=manufacturer,
merge_connections=merge_connections,
merge_identifiers=merge_identifiers,
model=model,
model_id=model_id,
name_by_user=name_by_user,
name=name,
new_connections=new_connections,
new_identifiers=new_identifiers,
remove_config_entry_id=remove_config_entry_id,
remove_config_subentry_id=remove_config_subentry_id,
serial_number=serial_number,
suggested_area=suggested_area,
sw_version=sw_version,
via_device_id=via_device_id,
)
@callback @callback
def _validate_connections( def _validate_connections(
self, self,

View File

@@ -330,7 +330,6 @@ async def test_snapshots(
device_dict.pop("_cache", None) device_dict.pop("_cache", None)
# This can be removed when suggested_area is removed from DeviceEntry # This can be removed when suggested_area is removed from DeviceEntry
device_dict.pop("_suggested_area") device_dict.pop("_suggested_area")
device_dict.pop("is_new")
devices.append({"device": device_dict, "entities": entities}) devices.append({"device": device_dict, "entities": entities})

View File

@@ -175,7 +175,6 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer):
serialized.pop("_cache") serialized.pop("_cache")
# This can be removed when suggested_area is removed from DeviceEntry # This can be removed when suggested_area is removed from DeviceEntry
serialized.pop("_suggested_area") serialized.pop("_suggested_area")
serialized.pop("is_new")
return cls._remove_created_and_modified_at(serialized) return cls._remove_created_and_modified_at(serialized)
@classmethod @classmethod