From 7b9b457f15a9193eac84c73aef7cb20f587802de Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:55:19 +0200 Subject: [PATCH] Migrate nuki to use runtime_data (#166943) --- homeassistant/components/nuki/__init__.py | 41 +++++-------------- .../components/nuki/binary_sensor.py | 8 ++-- homeassistant/components/nuki/coordinator.py | 17 +++++++- homeassistant/components/nuki/lock.py | 9 ++-- homeassistant/components/nuki/sensor.py | 8 ++-- 5 files changed, 35 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 6e89fd074b9..ae7f9fb4140 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio -from dataclasses import dataclass from http import HTTPStatus import logging @@ -14,7 +13,6 @@ from requests.exceptions import RequestException from homeassistant import exceptions from homeassistant.components import webhook -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_PORT, @@ -28,7 +26,7 @@ from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.update_coordinator import UpdateFailed from .const import CONF_ENCRYPT_TOKEN, DEFAULT_TIMEOUT, DOMAIN -from .coordinator import NukiCoordinator +from .coordinator import NukiConfigEntry, NukiCoordinator, NukiEntryData from .helpers import NukiWebhookException, parse_id _LOGGER = logging.getLogger(__name__) @@ -36,22 +34,12 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.BINARY_SENSOR, Platform.LOCK, Platform.SENSOR] -@dataclass(slots=True) -class NukiEntryData: - """Class to hold Nuki data.""" - - coordinator: NukiCoordinator - bridge: NukiBridge - locks: list[NukiLock] - openers: list[NukiOpener] - - def _get_bridge_devices(bridge: NukiBridge) -> tuple[list[NukiLock], list[NukiOpener]]: return bridge.locks, bridge.openers async def _create_webhook( - hass: HomeAssistant, entry: ConfigEntry, bridge: NukiBridge + hass: HomeAssistant, entry: NukiConfigEntry, bridge: NukiBridge ) -> None: # Create HomeAssistant webhook async def handle_webhook( @@ -63,16 +51,14 @@ async def _create_webhook( except ValueError: return web.Response(status=HTTPStatus.BAD_REQUEST) - entry_data: NukiEntryData = hass.data[DOMAIN][entry.entry_id] - locks = entry_data.locks - openers = entry_data.openers + locks = entry.runtime_data.locks + openers = entry.runtime_data.openers devices = [x for x in locks + openers if x.nuki_id == data["nukiId"]] if len(devices) == 1: devices[0].update_from_callback(data) - coordinator = entry_data.coordinator - coordinator.async_set_updated_data(None) + entry.runtime_data.coordinator.async_set_updated_data(None) return web.Response(status=HTTPStatus.OK) @@ -157,11 +143,9 @@ def _remove_webhook(bridge: NukiBridge, entry_id: str) -> None: bridge.callback_remove(item["id"]) -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: NukiConfigEntry) -> bool: """Set up the Nuki entry.""" - hass.data.setdefault(DOMAIN, {}) - # Migration of entry unique_id if isinstance(entry.unique_id, int): new_id = parse_id(entry.unique_id) @@ -225,7 +209,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) coordinator = NukiCoordinator(hass, entry, bridge, locks, openers) - hass.data[DOMAIN][entry.entry_id] = NukiEntryData( + entry.runtime_data = NukiEntryData( coordinator=coordinator, bridge=bridge, locks=locks, @@ -240,16 +224,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: NukiConfigEntry) -> bool: """Unload the Nuki entry.""" webhook.async_unregister(hass, entry.entry_id) - entry_data: NukiEntryData = hass.data[DOMAIN][entry.entry_id] try: async with asyncio.timeout(10): await hass.async_add_executor_job( _remove_webhook, - entry_data.bridge, + entry.runtime_data.bridge, entry.entry_id, ) except InvalidCredentialsException as err: @@ -261,8 +244,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: f"Unable to remove callback. Error communicating with Bridge: {err}" ) from err - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/nuki/binary_sensor.py b/homeassistant/components/nuki/binary_sensor.py index 7ba908c13e4..247ebfe0d71 100644 --- a/homeassistant/components/nuki/binary_sensor.py +++ b/homeassistant/components/nuki/binary_sensor.py @@ -9,23 +9,21 @@ from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import NukiEntryData -from .const import DOMAIN +from .coordinator import NukiConfigEntry from .entity import NukiEntity async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: NukiConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Nuki binary sensors.""" - entry_data: NukiEntryData = hass.data[DOMAIN][entry.entry_id] + entry_data = entry.runtime_data entities: list[NukiEntity] = [] diff --git a/homeassistant/components/nuki/coordinator.py b/homeassistant/components/nuki/coordinator.py index cccff99e397..36bed1b5d46 100644 --- a/homeassistant/components/nuki/coordinator.py +++ b/homeassistant/components/nuki/coordinator.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from collections import defaultdict +from dataclasses import dataclass from datetime import timedelta import logging @@ -25,16 +26,28 @@ _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = timedelta(seconds=30) +type NukiConfigEntry = ConfigEntry[NukiEntryData] + + +@dataclass(slots=True) +class NukiEntryData: + """Class to hold Nuki data.""" + + coordinator: NukiCoordinator + bridge: NukiBridge + locks: list[NukiLock] + openers: list[NukiOpener] + class NukiCoordinator(DataUpdateCoordinator[None]): """Data Update Coordinator for the Nuki integration.""" - config_entry: ConfigEntry + config_entry: NukiConfigEntry def __init__( self, hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: NukiConfigEntry, bridge: NukiBridge, locks: list[NukiLock], openers: list[NukiOpener], diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 95c01eac730..8ff36ba6f91 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -12,24 +12,23 @@ from requests.exceptions import RequestException import voluptuous as vol from homeassistant.components.lock import LockEntity, LockEntityFeature -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import NukiEntryData -from .const import ATTR_ENABLE, ATTR_UNLATCH, DOMAIN, ERROR_STATES +from .const import ATTR_ENABLE, ATTR_UNLATCH, ERROR_STATES +from .coordinator import NukiConfigEntry from .entity import NukiEntity from .helpers import CannotConnect async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: NukiConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Nuki lock platform.""" - entry_data: NukiEntryData = hass.data[DOMAIN][entry.entry_id] + entry_data = entry.runtime_data coordinator = entry_data.coordinator entities: list[NukiDeviceEntity] = [ diff --git a/homeassistant/components/nuki/sensor.py b/homeassistant/components/nuki/sensor.py index 46bb165543d..0f2a49a8b5e 100644 --- a/homeassistant/components/nuki/sensor.py +++ b/homeassistant/components/nuki/sensor.py @@ -9,23 +9,21 @@ from homeassistant.components.sensor import ( SensorEntity, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import NukiEntryData -from .const import DOMAIN +from .coordinator import NukiConfigEntry from .entity import NukiEntity async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: NukiConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Nuki lock sensor.""" - entry_data: NukiEntryData = hass.data[DOMAIN][entry.entry_id] + entry_data = entry.runtime_data async_add_entities( NukiBatterySensor(entry_data.coordinator, lock) for lock in entry_data.locks