diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 6fa8914e822..26e14a7642e 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -77,14 +77,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry, unique_id=f"{latitude}-{longitude}" ) - # identifiers in device_info should use Tuple[str, str, str] type, but latitude and + # identifiers in device_info should use tuple[str, str] type, but latitude and # longitude are float, so we convert old device entries to use correct types + # We used to use a str 3-tuple here sometime, convert that to a 2-tuple too. device_registry = await async_get_registry(hass) old_ids = (DOMAIN, latitude, longitude) - device_entry = device_registry.async_get_device({old_ids}) - if device_entry and entry.entry_id in device_entry.config_entries: - new_ids = (DOMAIN, str(latitude), str(longitude)) - device_registry.async_update_device(device_entry.id, new_identifiers={new_ids}) + for old_ids in ( + (DOMAIN, latitude, longitude), + ( + DOMAIN, + str(latitude), + str(longitude), + ), + ): + device_entry = device_registry.async_get_device({old_ids}) # type: ignore[arg-type] + if device_entry and entry.entry_id in device_entry.config_entries: + new_ids = (DOMAIN, f"{latitude}-{longitude}") + device_registry.async_update_device( + device_entry.id, new_identifiers={new_ids} + ) websession = async_get_clientsession(hass) diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index 190bc326d0c..337d3a723fa 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -109,8 +109,7 @@ class AirlyAirQuality(CoordinatorEntity, AirQualityEntity): "identifiers": { ( DOMAIN, - str(self.coordinator.latitude), - str(self.coordinator.longitude), + f"{self.coordinator.latitude}-{self.coordinator.longitude}", ) }, "name": DEFAULT_NAME, diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index a3d9a2981d2..b978afb25a9 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -100,8 +100,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity): "identifiers": { ( DOMAIN, - str(self.coordinator.latitude), - str(self.coordinator.longitude), + f"{self.coordinator.latitude}-{self.coordinator.longitude}", ) }, "name": DEFAULT_NAME, diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 7bd30cbdbf6..f36ae97840b 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -163,7 +163,7 @@ class Router: return DEFAULT_DEVICE_NAME @property - def device_identifiers(self) -> set[tuple[str, ...]]: + def device_identifiers(self) -> set[tuple[str, str]]: """Get router identifiers for device registry.""" try: return {(DOMAIN, self.data[KEY_DEVICE_INFORMATION]["SerialNumber"])} diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py index 5c28e6f029a..9045b82e2ac 100644 --- a/homeassistant/components/syncthru/__init__.py +++ b/homeassistant/components/syncthru/__init__.py @@ -76,7 +76,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -def device_identifiers(printer: SyncThru) -> set[tuple[str, ...]] | None: +def device_identifiers(printer: SyncThru) -> set[tuple[str, str]] | None: """Get device identifiers for device registry.""" serial = printer.serial_number() if serial is None: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index a448fd1c198..9f09bbbf642 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -45,7 +45,7 @@ ORPHANED_DEVICE_KEEP_SECONDS = 86400 * 30 class _DeviceIndex(NamedTuple): - identifiers: dict[tuple[str, ...], str] + identifiers: dict[tuple[str, str], str] connections: dict[tuple[str, str], str] @@ -55,7 +55,7 @@ class DeviceEntry: config_entries: set[str] = attr.ib(converter=set, factory=set) connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set) - identifiers: set[tuple[str, ...]] = attr.ib(converter=set, factory=set) + identifiers: set[tuple[str, str]] = attr.ib(converter=set, factory=set) manufacturer: str | None = attr.ib(default=None) model: str | None = attr.ib(default=None) name: str | None = attr.ib(default=None) @@ -92,7 +92,7 @@ class DeletedDeviceEntry: config_entries: set[str] = attr.ib() connections: set[tuple[str, str]] = attr.ib() - identifiers: set[tuple[str, ...]] = attr.ib() + identifiers: set[tuple[str, str]] = attr.ib() id: str = attr.ib() orphaned_timestamp: float | None = attr.ib() @@ -100,7 +100,7 @@ class DeletedDeviceEntry: self, config_entry_id: str, connections: set[tuple[str, str]], - identifiers: set[tuple[str, ...]], + identifiers: set[tuple[str, str]], ) -> DeviceEntry: """Create DeviceEntry from DeletedDeviceEntry.""" return DeviceEntry( @@ -135,7 +135,7 @@ def format_mac(mac: str) -> str: def _async_get_device_id_from_index( devices_index: _DeviceIndex, - identifiers: set[tuple[str, ...]], + identifiers: set[tuple[str, str]], connections: set[tuple[str, str]] | None, ) -> str | None: """Check if device has previously been registered.""" @@ -172,7 +172,7 @@ class DeviceRegistry: @callback def async_get_device( self, - identifiers: set[tuple[str, ...]], + identifiers: set[tuple[str, str]], connections: set[tuple[str, str]] | None = None, ) -> DeviceEntry | None: """Check if device is registered.""" @@ -185,7 +185,7 @@ class DeviceRegistry: def _async_get_deleted_device( self, - identifiers: set[tuple[str, ...]], + identifiers: set[tuple[str, str]], connections: set[tuple[str, str]] | None, ) -> DeletedDeviceEntry | None: """Check if device is deleted.""" @@ -245,7 +245,7 @@ class DeviceRegistry: *, config_entry_id: str, connections: set[tuple[str, str]] | None = None, - identifiers: set[tuple[str, ...]] | None = None, + identifiers: set[tuple[str, str]] | None = None, manufacturer: str | None | UndefinedType = UNDEFINED, model: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, @@ -329,7 +329,7 @@ class DeviceRegistry: model: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, name_by_user: str | None | UndefinedType = UNDEFINED, - new_identifiers: set[tuple[str, ...]] | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, sw_version: str | None | UndefinedType = UNDEFINED, via_device_id: str | None | UndefinedType = UNDEFINED, remove_config_entry_id: str | UndefinedType = UNDEFINED, @@ -360,8 +360,8 @@ class DeviceRegistry: add_config_entry_id: str | UndefinedType = UNDEFINED, remove_config_entry_id: str | UndefinedType = UNDEFINED, merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, - merge_identifiers: set[tuple[str, ...]] | UndefinedType = UNDEFINED, - new_identifiers: set[tuple[str, ...]] | UndefinedType = UNDEFINED, + merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, manufacturer: str | None | UndefinedType = UNDEFINED, model: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, @@ -519,7 +519,7 @@ class DeviceRegistry: config_entries=set(device["config_entries"]), # 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] - identifiers={tuple(iden) for iden in device["identifiers"]}, + identifiers={tuple(iden) for iden in device["identifiers"]}, # type: ignore[misc] id=device["id"], # Introduced in 2021.2 orphaned_timestamp=device.get("orphaned_timestamp"), diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index d9b3b28a9ef..a9843387fd9 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -115,7 +115,7 @@ class DeviceInfo(TypedDict, total=False): name: str connections: set[tuple[str, str]] - identifiers: set[tuple[str, ...]] + identifiers: set[tuple[str, str]] manufacturer: str model: str suggested_area: str diff --git a/tests/components/airly/test_init.py b/tests/components/airly/test_init.py index 5dcd465774d..da545eb5193 100644 --- a/tests/components/airly/test_init.py +++ b/tests/components/airly/test_init.py @@ -1,6 +1,8 @@ """Test init of Airly integration.""" from unittest.mock import patch +import pytest + from homeassistant.components.airly import set_update_interval from homeassistant.components.airly.const import DOMAIN from homeassistant.config_entries import ( @@ -188,7 +190,8 @@ async def test_unload_entry(hass, aioclient_mock): assert not hass.data.get(DOMAIN) -async def test_migrate_device_entry(hass, aioclient_mock): +@pytest.mark.parametrize("old_identifier", ((DOMAIN, 123, 456), (DOMAIN, "123", "456"))) +async def test_migrate_device_entry(hass, aioclient_mock, old_identifier): """Test device_info identifiers migration.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -207,13 +210,13 @@ async def test_migrate_device_entry(hass, aioclient_mock): device_reg = mock_device_registry(hass) device_entry = device_reg.async_get_or_create( - config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, 123, 456)} + config_entry_id=config_entry.entry_id, identifiers={old_identifier} ) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() migrated_device_entry = device_reg.async_get_or_create( - config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "123", "456")} + config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "123-456")} ) assert device_entry.id == migrated_device_entry.id