mirror of
https://github.com/home-assistant/core.git
synced 2025-08-02 10:08:23 +00:00
Make device suggested_area only influence new devices (#149758)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
parent
a08c3c9f44
commit
f538807d6e
@ -906,7 +906,19 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
|||||||
if device is None:
|
if device is None:
|
||||||
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:
|
||||||
device = DeviceEntry(is_new=True)
|
area_id: str | None = None
|
||||||
|
if (
|
||||||
|
suggested_area is not None
|
||||||
|
and suggested_area is not UNDEFINED
|
||||||
|
and suggested_area != ""
|
||||||
|
):
|
||||||
|
# Circular dep
|
||||||
|
from . import area_registry as ar # noqa: PLC0415
|
||||||
|
|
||||||
|
area = ar.async_get(self.hass).async_get_or_create(suggested_area)
|
||||||
|
area_id = area.id
|
||||||
|
device = DeviceEntry(is_new=True, area_id=area_id)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.deleted_devices.pop(deleted_device.id)
|
self.deleted_devices.pop(deleted_device.id)
|
||||||
device = deleted_device.to_device_entry(
|
device = deleted_device.to_device_entry(
|
||||||
@ -961,7 +973,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,
|
||||||
)
|
)
|
||||||
@ -1000,6 +1012,10 @@ 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
|
||||||
|
# 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,
|
||||||
@ -1065,19 +1081,6 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
|||||||
"Cannot define both merge_identifiers and new_identifiers"
|
"Cannot define both merge_identifiers and new_identifiers"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
suggested_area is not None
|
|
||||||
and suggested_area is not UNDEFINED
|
|
||||||
and suggested_area != ""
|
|
||||||
and area_id is UNDEFINED
|
|
||||||
and old.area_id is None
|
|
||||||
):
|
|
||||||
# Circular dep
|
|
||||||
from . import area_registry as ar # noqa: PLC0415
|
|
||||||
|
|
||||||
area = ar.async_get(self.hass).async_get_or_create(suggested_area)
|
|
||||||
area_id = area.id
|
|
||||||
|
|
||||||
if add_config_entry_id is not UNDEFINED:
|
if add_config_entry_id is not UNDEFINED:
|
||||||
if add_config_subentry_id is UNDEFINED:
|
if add_config_subentry_id is UNDEFINED:
|
||||||
# Interpret not specifying a subentry as None (the main entry)
|
# Interpret not specifying a subentry as None (the main entry)
|
||||||
@ -1155,6 +1158,16 @@ 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
|
||||||
|
|
||||||
|
@ -3210,20 +3210,35 @@ async def test_update_remove_config_subentries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("initial_area", "device_area_id", "number_of_areas"),
|
||||||
|
[
|
||||||
|
(None, None, 0),
|
||||||
|
("Living Room", "living_room", 1),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_update_suggested_area(
|
async def test_update_suggested_area(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
area_registry: ar.AreaRegistry,
|
area_registry: ar.AreaRegistry,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
initial_area: str | None,
|
||||||
|
device_area_id: str | None,
|
||||||
|
number_of_areas: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Verify that we can update the suggested area version of a device."""
|
"""Verify that we can update the suggested area of a device.
|
||||||
|
|
||||||
|
Updating the suggested area of a device should not create a new area, nor should
|
||||||
|
it change the area_id of the device.
|
||||||
|
"""
|
||||||
update_events = async_capture_events(hass, dr.EVENT_DEVICE_REGISTRY_UPDATED)
|
update_events = async_capture_events(hass, dr.EVENT_DEVICE_REGISTRY_UPDATED)
|
||||||
entry = device_registry.async_get_or_create(
|
entry = device_registry.async_get_or_create(
|
||||||
config_entry_id=mock_config_entry.entry_id,
|
config_entry_id=mock_config_entry.entry_id,
|
||||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||||
identifiers={("bla", "123")},
|
identifiers={("bla", "123")},
|
||||||
|
suggested_area=initial_area,
|
||||||
)
|
)
|
||||||
assert entry.area_id is None
|
assert entry.area_id == device_area_id
|
||||||
|
|
||||||
suggested_area = "Pool"
|
suggested_area = "Pool"
|
||||||
|
|
||||||
@ -3232,26 +3247,24 @@ async def test_update_suggested_area(
|
|||||||
entry.id, suggested_area=suggested_area
|
entry.id, suggested_area=suggested_area
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_save.call_count == 1
|
# Check the device registry was not saved
|
||||||
|
assert mock_save.call_count == 0
|
||||||
assert updated_entry != entry
|
assert updated_entry != entry
|
||||||
|
assert updated_entry.area_id == device_area_id
|
||||||
|
|
||||||
pool_area = area_registry.async_get_area_by_name("Pool")
|
# Check we did not create an area
|
||||||
assert pool_area is not None
|
pool_area = area_registry.async_get_area_by_name(suggested_area)
|
||||||
assert updated_entry.area_id == pool_area.id
|
assert pool_area is None
|
||||||
assert len(area_registry.areas) == 1
|
assert updated_entry.area_id == device_area_id
|
||||||
|
assert len(area_registry.areas) == number_of_areas
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(update_events) == 2
|
assert len(update_events) == 1
|
||||||
assert update_events[0].data == {
|
assert update_events[0].data == {
|
||||||
"action": "create",
|
"action": "create",
|
||||||
"device_id": entry.id,
|
"device_id": entry.id,
|
||||||
}
|
}
|
||||||
assert update_events[1].data == {
|
|
||||||
"action": "update",
|
|
||||||
"device_id": entry.id,
|
|
||||||
"changes": {"area_id": None, "suggested_area": None},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Do not save or fire the event if the suggested
|
# Do not save or fire the event if the suggested
|
||||||
# area does not result in a change of area
|
# area does not result in a change of area
|
||||||
@ -3260,10 +3273,10 @@ async def test_update_suggested_area(
|
|||||||
updated_entry = device_registry.async_update_device(
|
updated_entry = device_registry.async_update_device(
|
||||||
entry.id, suggested_area="Other"
|
entry.id, suggested_area="Other"
|
||||||
)
|
)
|
||||||
assert len(update_events) == 2
|
assert len(update_events) == 1
|
||||||
assert mock_save_2.call_count == 0
|
assert mock_save_2.call_count == 0
|
||||||
assert updated_entry != entry
|
assert updated_entry != entry
|
||||||
assert updated_entry.area_id == pool_area.id
|
assert updated_entry.area_id == device_area_id
|
||||||
|
|
||||||
|
|
||||||
async def test_cleanup_device_registry(
|
async def test_cleanup_device_registry(
|
||||||
@ -3397,11 +3410,13 @@ async def test_cleanup_entity_registry_change(
|
|||||||
assert len(mock_call.mock_calls) == 2
|
assert len(mock_call.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("initial_area", [None, "12345A"])
|
||||||
@pytest.mark.usefixtures("freezer")
|
@pytest.mark.usefixtures("freezer")
|
||||||
async def test_restore_device(
|
async def test_restore_device(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
mock_config_entry_with_subentries: MockConfigEntry,
|
mock_config_entry_with_subentries: MockConfigEntry,
|
||||||
|
initial_area: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Make sure device id is stable."""
|
"""Make sure device id is stable."""
|
||||||
entry_id = mock_config_entry_with_subentries.entry_id
|
entry_id = mock_config_entry_with_subentries.entry_id
|
||||||
@ -3428,7 +3443,7 @@ async def test_restore_device(
|
|||||||
# Apply user customizations
|
# Apply user customizations
|
||||||
entry = device_registry.async_update_device(
|
entry = device_registry.async_update_device(
|
||||||
entry.id,
|
entry.id,
|
||||||
area_id="12345A",
|
area_id=initial_area,
|
||||||
disabled_by=dr.DeviceEntryDisabler.USER,
|
disabled_by=dr.DeviceEntryDisabler.USER,
|
||||||
labels={"label1", "label2"},
|
labels={"label1", "label2"},
|
||||||
name_by_user="Test Friendly Name",
|
name_by_user="Test Friendly Name",
|
||||||
@ -3493,7 +3508,7 @@ async def test_restore_device(
|
|||||||
via_device="via_device_id_new",
|
via_device="via_device_id_new",
|
||||||
)
|
)
|
||||||
assert entry3 == dr.DeviceEntry(
|
assert entry3 == dr.DeviceEntry(
|
||||||
area_id="12345A",
|
area_id=initial_area,
|
||||||
config_entries={entry_id},
|
config_entries={entry_id},
|
||||||
config_entries_subentries={entry_id: {subentry_id}},
|
config_entries_subentries={entry_id: {subentry_id}},
|
||||||
configuration_url="http://config_url_new.bla",
|
configuration_url="http://config_url_new.bla",
|
||||||
@ -4933,3 +4948,11 @@ async def test_suggested_area_deprecation(
|
|||||||
"The deprecated function suggested_area was called. It will be removed in "
|
"The deprecated function suggested_area was called. It will be removed in "
|
||||||
"HA Core 2026.9. Use code which ignores suggested_area instead"
|
"HA Core 2026.9. Use code which ignores suggested_area instead"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
device_registry.async_update_device(entry.id, suggested_area="TV Room")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Detected code that passes a suggested_area to device_registry.async_update "
|
||||||
|
"device. This will stop working in Home Assistant 2026.9.0, please report "
|
||||||
|
"this issue"
|
||||||
|
) in caplog.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user