Skip to content

Commit

Permalink
Merge branch 'dev' into onedrive/metadata-files
Browse files Browse the repository at this point in the history
  • Loading branch information
zweckj authored Feb 7, 2025
2 parents 4e65d46 + 6d6961a commit ae5789d
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 3 deletions.
42 changes: 39 additions & 3 deletions homeassistant/helpers/device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from collections import defaultdict
from collections.abc import Mapping
from collections.abc import Iterable, Mapping
from datetime import datetime
from enum import StrEnum
from functools import lru_cache
Expand Down Expand Up @@ -561,6 +561,21 @@ def get_entry(
return self._connections[connection]
return None

def get_entries(
self,
identifiers: set[tuple[str, str]] | None,
connections: set[tuple[str, str]] | None,
) -> Iterable[_EntryTypeT]:
"""Get entries from identifiers or connections."""
if identifiers:
for identifier in identifiers:
if identifier in self._identifiers:
yield self._identifiers[identifier]
if connections:
for connection in _normalize_connections(connections):
if connection in self._connections:
yield self._connections[connection]


class ActiveDeviceRegistryItems(DeviceRegistryItems[DeviceEntry]):
"""Container for active (non-deleted) device registry entries."""
Expand Down Expand Up @@ -667,6 +682,14 @@ def _async_get_deleted_device(
"""Check if device is deleted."""
return self.deleted_devices.get_entry(identifiers, connections)

def _async_get_deleted_devices(
self,
identifiers: set[tuple[str, str]] | None = None,
connections: set[tuple[str, str]] | None = None,
) -> Iterable[DeletedDeviceEntry]:
"""List devices that are deleted."""
return self.deleted_devices.get_entries(identifiers, connections)

def _substitute_name_placeholders(
self,
domain: str,
Expand Down Expand Up @@ -958,6 +981,9 @@ def async_update_device( # noqa: C901
new_values["config_entries"] = config_entries
old_values["config_entries"] = old.config_entries

added_connections: set[tuple[str, str]] | None = None
added_identifiers: set[tuple[str, str]] | None = None

if merge_connections is not UNDEFINED:
normalized_connections = self._validate_connections(
device_id,
Expand All @@ -966,6 +992,7 @@ def async_update_device( # noqa: C901
)
old_connections = old.connections
if not normalized_connections.issubset(old_connections):
added_connections = normalized_connections
new_values["connections"] = old_connections | normalized_connections
old_values["connections"] = old_connections

Expand All @@ -975,17 +1002,18 @@ def async_update_device( # noqa: C901
)
old_identifiers = old.identifiers
if not merge_identifiers.issubset(old_identifiers):
added_identifiers = merge_identifiers
new_values["identifiers"] = old_identifiers | merge_identifiers
old_values["identifiers"] = old_identifiers

if new_connections is not UNDEFINED:
new_values["connections"] = self._validate_connections(
added_connections = new_values["connections"] = self._validate_connections(
device_id, new_connections, False
)
old_values["connections"] = old.connections

if new_identifiers is not UNDEFINED:
new_values["identifiers"] = self._validate_identifiers(
added_identifiers = new_values["identifiers"] = self._validate_identifiers(
device_id, new_identifiers, False
)
old_values["identifiers"] = old.identifiers
Expand Down Expand Up @@ -1028,6 +1056,14 @@ def async_update_device( # noqa: C901
new = attr.evolve(old, **new_values)
self.devices[device_id] = new

# NOTE: Once we solve the broader issue of duplicated devices, we might
# want to revisit it. Instead of simply removing the duplicated deleted device,
# we might want to merge the information from it into the non-deleted device.
for deleted_device in self._async_get_deleted_devices(
added_identifiers, added_connections
):
del self.deleted_devices[deleted_device.id]

# If its only run time attributes (suggested_area)
# that do not get saved we do not want to write
# to disk or fire an event as we would end up
Expand Down
33 changes: 33 additions & 0 deletions tests/helpers/test_device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3378,6 +3378,39 @@ async def test_device_registry_identifiers_collision(
assert not device1_refetched.identifiers.isdisjoint(device3_refetched.identifiers)


async def test_device_registry_deleted_device_collision(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
) -> None:
"""Test update collisions with deleted devices in the device registry."""
config_entry = MockConfigEntry()
config_entry.add_to_hass(hass)

device1 = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "EE:EE:EE:EE:EE:EE")},
manufacturer="manufacturer",
model="model",
)
assert len(device_registry.deleted_devices) == 0

device_registry.async_remove_device(device1.id)
assert len(device_registry.deleted_devices) == 1

device2 = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={("bridgeid", "0123")},
manufacturer="manufacturer",
model="model",
)
assert len(device_registry.deleted_devices) == 1

device_registry.async_update_device(
device2.id,
merge_connections={(dr.CONNECTION_NETWORK_MAC, "EE:EE:EE:EE:EE:EE")},
)
assert len(device_registry.deleted_devices) == 0


async def test_primary_config_entry(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
Expand Down

0 comments on commit ae5789d

Please sign in to comment.