mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Update and migrate ring non string unique ids (#115265)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
80450adb1a
commit
4cd2351bcc
@ -9,8 +9,8 @@ from ring_doorbell import Auth, Ring
|
|||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__
|
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -44,6 +44,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
ring = Ring(auth)
|
ring = Ring(auth)
|
||||||
|
|
||||||
|
await _migrate_old_unique_ids(hass, entry.entry_id)
|
||||||
|
|
||||||
devices_coordinator = RingDataCoordinator(hass, ring)
|
devices_coordinator = RingDataCoordinator(hass, ring)
|
||||||
notifications_coordinator = RingNotificationsCoordinator(hass, ring)
|
notifications_coordinator = RingNotificationsCoordinator(hass, ring)
|
||||||
await devices_coordinator.async_config_entry_first_refresh()
|
await devices_coordinator.async_config_entry_first_refresh()
|
||||||
@ -111,3 +113,28 @@ async def async_remove_config_entry_device(
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
"""Remove a config entry from a device."""
|
"""Remove a config entry from a device."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _migrate_old_unique_ids(hass: HomeAssistant, entry_id: str) -> None:
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
||||||
|
# Old format for camera and light was int
|
||||||
|
if isinstance(entity_entry.unique_id, int):
|
||||||
|
new_unique_id = str(entity_entry.unique_id)
|
||||||
|
if existing_entity_id := entity_registry.async_get_entity_id(
|
||||||
|
entity_entry.domain, entity_entry.platform, new_unique_id
|
||||||
|
):
|
||||||
|
_LOGGER.error(
|
||||||
|
"Cannot migrate to unique_id '%s', already exists for '%s', "
|
||||||
|
"You may have to delete unavailable ring entities",
|
||||||
|
new_unique_id,
|
||||||
|
existing_entity_id,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
_LOGGER.info("Fixing non string unique id %s", entity_entry.unique_id)
|
||||||
|
return {"new_unique_id": new_unique_id}
|
||||||
|
return None
|
||||||
|
|
||||||
|
await er.async_migrate_entries(hass, entry_id, _async_migrator)
|
||||||
|
@ -67,7 +67,7 @@ class RingCam(RingEntity, Camera):
|
|||||||
self._video_url = None
|
self._video_url = None
|
||||||
self._image = None
|
self._image = None
|
||||||
self._expires_at = dt_util.utcnow() - FORCE_REFRESH_INTERVAL
|
self._expires_at = dt_util.utcnow() - FORCE_REFRESH_INTERVAL
|
||||||
self._attr_unique_id = device.id
|
self._attr_unique_id = str(device.id)
|
||||||
if device.has_capability(MOTION_DETECTION_CAPABILITY):
|
if device.has_capability(MOTION_DETECTION_CAPABILITY):
|
||||||
self._attr_motion_detection_enabled = device.motion_detection
|
self._attr_motion_detection_enabled = device.motion_detection
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class RingLight(RingEntity, LightEntity):
|
|||||||
def __init__(self, device, coordinator):
|
def __init__(self, device, coordinator):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
super().__init__(device, coordinator)
|
super().__init__(device, coordinator)
|
||||||
self._attr_unique_id = device.id
|
self._attr_unique_id = str(device.id)
|
||||||
self._attr_is_on = device.lights == ON_STATE
|
self._attr_is_on = device.lights == ON_STATE
|
||||||
self._no_updates_until = dt_util.utcnow()
|
self._no_updates_until = dt_util.utcnow()
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ async def test_entity_registry(
|
|||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
entry = entity_registry.async_get("camera.front")
|
entry = entity_registry.async_get("camera.front")
|
||||||
assert entry.unique_id == 765432
|
assert entry.unique_id == "765432"
|
||||||
|
|
||||||
entry = entity_registry.async_get("camera.internal")
|
entry = entity_registry.async_get("camera.internal")
|
||||||
assert entry.unique_id == 345678
|
assert entry.unique_id == "345678"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -8,9 +8,13 @@ import requests_mock
|
|||||||
from ring_doorbell import AuthenticationError, RingError, RingTimeout
|
from ring_doorbell import AuthenticationError, RingError, RingTimeout
|
||||||
|
|
||||||
from homeassistant.components import ring
|
from homeassistant.components import ring
|
||||||
|
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||||
|
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||||
from homeassistant.components.ring import DOMAIN
|
from homeassistant.components.ring import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.issue_registry import IssueRegistry
|
from homeassistant.helpers.issue_registry import IssueRegistry
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@ -253,7 +257,7 @@ async def test_issue_deprecated_service_ring_update(
|
|||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
_ = await hass.services.async_call(DOMAIN, "update", {}, blocking=True)
|
await hass.services.async_call(DOMAIN, "update", {}, blocking=True)
|
||||||
|
|
||||||
issue = issue_registry.async_get_issue("ring", "deprecated_service_ring_update")
|
issue = issue_registry.async_get_issue("ring", "deprecated_service_ring_update")
|
||||||
assert issue
|
assert issue
|
||||||
@ -266,3 +270,135 @@ async def test_issue_deprecated_service_ring_update(
|
|||||||
"This is deprecated and will stop working in Home Assistant 2024.10. "
|
"This is deprecated and will stop working in Home Assistant 2024.10. "
|
||||||
"Use 'homeassistant.update_entity' instead which updates all ring entities"
|
"Use 'homeassistant.update_entity' instead which updates all ring entities"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("domain", "old_unique_id"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
123456,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
CAMERA_DOMAIN,
|
||||||
|
654321,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_update_unique_id(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
requests_mock: requests_mock.Mocker,
|
||||||
|
domain: str,
|
||||||
|
old_unique_id: int | str,
|
||||||
|
) -> None:
|
||||||
|
"""Test unique_id update of integration."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
title="Ring",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: "foo@bar.com",
|
||||||
|
"token": {"access_token": "mock-token"},
|
||||||
|
},
|
||||||
|
unique_id="foo@bar.com",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
domain=domain,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=old_unique_id,
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
assert entity.unique_id == old_unique_id
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entity_migrated
|
||||||
|
assert entity_migrated.unique_id == str(old_unique_id)
|
||||||
|
assert (f"Fixing non string unique id {old_unique_id}") in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_unique_id_existing(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
requests_mock: requests_mock.Mocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test unique_id update of integration."""
|
||||||
|
old_unique_id = 123456
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
title="Ring",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: "foo@bar.com",
|
||||||
|
"token": {"access_token": "mock-token"},
|
||||||
|
},
|
||||||
|
unique_id="foo@bar.com",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
domain=CAMERA_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=old_unique_id,
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
entity_existing = entity_registry.async_get_or_create(
|
||||||
|
domain=CAMERA_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=str(old_unique_id),
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
assert entity.unique_id == old_unique_id
|
||||||
|
assert entity_existing.unique_id == str(old_unique_id)
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_not_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
entity_existing = entity_registry.async_get(entity_existing.entity_id)
|
||||||
|
assert entity_not_migrated
|
||||||
|
assert entity_existing
|
||||||
|
assert entity_not_migrated.unique_id == old_unique_id
|
||||||
|
assert (
|
||||||
|
f"Cannot migrate to unique_id '{old_unique_id}', "
|
||||||
|
f"already exists for '{entity_existing.entity_id}', "
|
||||||
|
"You may have to delete unavailable ring entities"
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_unique_id_no_update(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
requests_mock: requests_mock.Mocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test unique_id update of integration."""
|
||||||
|
correct_unique_id = "123456"
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
title="Ring",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: "foo@bar.com",
|
||||||
|
"token": {"access_token": "mock-token"},
|
||||||
|
},
|
||||||
|
unique_id="foo@bar.com",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
domain=CAMERA_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id="123456",
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
assert entity.unique_id == correct_unique_id
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entity_migrated
|
||||||
|
assert entity_migrated.unique_id == correct_unique_id
|
||||||
|
assert "Fixing non string unique id" not in caplog.text
|
||||||
|
@ -25,10 +25,10 @@ async def test_entity_registry(
|
|||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
entry = entity_registry.async_get("light.front_light")
|
entry = entity_registry.async_get("light.front_light")
|
||||||
assert entry.unique_id == 765432
|
assert entry.unique_id == "765432"
|
||||||
|
|
||||||
entry = entity_registry.async_get("light.internal_light")
|
entry = entity_registry.async_get("light.internal_light")
|
||||||
assert entry.unique_id == 345678
|
assert entry.unique_id == "345678"
|
||||||
|
|
||||||
|
|
||||||
async def test_light_off_reports_correctly(
|
async def test_light_off_reports_correctly(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user