From eda44ca5b76bb5d04bcb29e8aa7d7f14c46b8fb2 Mon Sep 17 00:00:00 2001 From: Julian De Vita Date: Mon, 14 Sep 2026 18:46:56 -0400 Subject: [PATCH] fix: avoid deprecated device_registry.devices mapping access (#91) PR #90 replaced the deprecated `async_get_device(identifiers=...)` lookup in cleanup_old_device() with an iteration over `device_reg.devices.values()`. That swapped one deprecation for another: HA 2026.9 wraps `DeviceRegistry.devices` in a compatibility view that reports any mapping-style use and stops working in HA Core 2027.9.0, so the integration logs a warning on every startup. Walk this integration's own config entries and use the supported `dr.async_entries_for_config_entry()` helper instead, which reads the registry's internal container directly and is not deprecated. Iterating every config entry of the domain (rather than just the one being set up) keeps the previous behaviour of finding the legacy device regardless of which entry owns it. Also adds a regression test for cleanup_old_device(), which had no coverage despite being rewritten twice, and syncs the manifest version with tag v1.8.3. --- custom_components/parcelapp/__init__.py | 34 ++++++++++++++--------- custom_components/parcelapp/manifest.json | 2 +- tests/test_init.py | 32 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 tests/test_init.py diff --git a/custom_components/parcelapp/__init__.py b/custom_components/parcelapp/__init__.py index d8d7f39..7a8f852 100644 --- a/custom_components/parcelapp/__init__.py +++ b/custom_components/parcelapp/__init__.py @@ -81,19 +81,27 @@ async def cleanup_old_device(hass: HomeAssistant) -> None: Older versions of this integration registered a device using a malformed identifier -- a bare ``(DOMAIN,)`` tuple instead of the expected - ``(DOMAIN, )``. ``device_registry.async_get_device`` is - deprecated (scheduled for removal in HA Core 2027.8.0) because device - identifiers are no longer guaranteed unique across config entries, and it - cannot be used to look up the malformed single-element identifier anyway. - - Iterate the device registry and remove any device that still carries the - improper identifier. + ``(DOMAIN, )``. That device cannot be looked up with + ``device_registry.async_get_device`` (deprecated, and it requires a + well-formed identifier pair), and using ``device_registry.devices`` as a + mapping is deprecated too (stops working in HA Core 2027.9.0). + + Walk this integration's own config entries with the supported + ``async_entries_for_config_entry`` helper and remove any device that still + carries the improper identifier. """ device_reg = dr.async_get(hass) - for device in list(device_reg.devices.values()): - if any( - len(identifier) == 1 and identifier[0] == DOMAIN - for identifier in device.identifiers + removed: set[str] = set() + for config_entry in hass.config_entries.async_entries(DOMAIN): + for device in dr.async_entries_for_config_entry( + device_reg, config_entry.entry_id ): - _LOGGER.debug("Removing improper device %s", device.name) - device_reg.async_remove_device(device.id) + if device.id in removed: + continue + if any( + len(identifier) == 1 and identifier[0] == DOMAIN + for identifier in device.identifiers + ): + _LOGGER.debug("Removing improper device %s", device.name) + device_reg.async_remove_device(device.id) + removed.add(device.id) diff --git a/custom_components/parcelapp/manifest.json b/custom_components/parcelapp/manifest.json index b9e21f9..74c619d 100644 --- a/custom_components/parcelapp/manifest.json +++ b/custom_components/parcelapp/manifest.json @@ -11,5 +11,5 @@ "requests>=2.32.3", "python-dateutil>=2.9.0" ], - "version": "1.8.2" + "version": "1.8.3" } diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 0000000..74acc48 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,32 @@ +"""Tests for the integration setup helpers.""" + +import pytest +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.parcelapp import cleanup_old_device +from custom_components.parcelapp.const import DOMAIN + + +@pytest.mark.asyncio +async def test_cleanup_removes_only_the_malformed_device(hass: HomeAssistant) -> None: + """The legacy bare (DOMAIN,) device is removed, a well-formed one is kept.""" + entry = MockConfigEntry( + domain=DOMAIN, entry_id="entry_1", data={"api_key": "test_api_key"} + ) + entry.add_to_hass(hass) + device_reg = dr.async_get(hass) + legacy = device_reg.async_get_or_create( + config_entry_id=entry.entry_id, identifiers={(DOMAIN,)}, name="Legacy" + ) + current = device_reg.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, entry.entry_id)}, + name="Parcel", + ) + + await cleanup_old_device(hass) + + assert device_reg.async_get(legacy.id) is None + assert device_reg.async_get(current.id) is not None