From 16d86e5d4cef496f34f8843014b3aac80ba26259 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 7 May 2024 14:10:44 +0200 Subject: [PATCH] Store Philips TV runtime data in config entry (#116952) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- .../components/philips_js/__init__.py | 17 ++- .../components/philips_js/binary_sensor.py | 10 +- .../components/philips_js/diagnostics.py | 8 +- homeassistant/components/philips_js/light.py | 8 +- .../components/philips_js/media_player.py | 8 +- homeassistant/components/philips_js/remote.py | 8 +- homeassistant/components/philips_js/switch.py | 10 +- .../snapshots/test_diagnostics.ambr | 100 ++++++++++++++++++ .../components/philips_js/test_diagnostics.py | 66 ++++++++++++ 9 files changed, 191 insertions(+), 44 deletions(-) create mode 100644 tests/components/philips_js/snapshots/test_diagnostics.ambr create mode 100644 tests/components/philips_js/test_diagnostics.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index e56d1cdc651..ee7059d25bf 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -42,8 +42,10 @@ PLATFORMS = [ LOGGER = logging.getLogger(__name__) +PhilipsTVConfigEntry = ConfigEntry["PhilipsTVDataUpdateCoordinator"] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + +async def async_setup_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) -> bool: """Set up Philips TV from a config entry.""" system: SystemType | None = entry.data.get(CONF_SYSTEM) @@ -62,8 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = {**entry.data, CONF_SYSTEM: actual_system} hass.config_entries.async_update_entry(entry, data=data) - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = coordinator + entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -72,18 +73,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_update_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: +async def async_update_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) -> None: """Update options.""" await hass.config_entries.async_reload(entry.entry_id) -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) -> bool: """Unload a config entry.""" - 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) class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-enforce-coordinator-module diff --git a/homeassistant/components/philips_js/binary_sensor.py b/homeassistant/components/philips_js/binary_sensor.py index a21d1416192..5e8c10ec06a 100644 --- a/homeassistant/components/philips_js/binary_sensor.py +++ b/homeassistant/components/philips_js/binary_sensor.py @@ -10,12 +10,10 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity @@ -42,13 +40,11 @@ DESCRIPTIONS = ( async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: PhilipsTVConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the configuration entry.""" - coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][ - config_entry.entry_id - ] + coordinator = config_entry.runtime_data if ( coordinator.api.json_feature_supported("recordings", "List") diff --git a/homeassistant/components/philips_js/diagnostics.py b/homeassistant/components/philips_js/diagnostics.py index 34cc71c9b94..625b77f6c25 100644 --- a/homeassistant/components/philips_js/diagnostics.py +++ b/homeassistant/components/philips_js/diagnostics.py @@ -5,11 +5,9 @@ from __future__ import annotations from typing import Any from homeassistant.components.diagnostics import async_redact_data -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from . import PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import PhilipsTVConfigEntry TO_REDACT = { "serialnumber_encrypted", @@ -24,10 +22,10 @@ TO_REDACT = { async def async_get_config_entry_diagnostics( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: PhilipsTVConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator = entry.runtime_data api = coordinator.api return { diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 6a91b872913..27b0522debb 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -16,14 +16,12 @@ from homeassistant.components.light import ( LightEntity, LightEntityFeature, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv -from . import PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity EFFECT_PARTITION = ": " @@ -35,11 +33,11 @@ EFFECT_EXPERT_STYLES = {"FOLLOW_AUDIO", "FOLLOW_COLOR", "Lounge light"} async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: PhilipsTVConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the configuration entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = config_entry.runtime_data async_add_entities([PhilipsTVLightEntity(coordinator)]) diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index c8b89d57854..ab71f8bb727 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -16,14 +16,12 @@ from homeassistant.components.media_player import ( MediaPlayerState, MediaType, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.trigger import PluggableAction -from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import LOGGER as _LOGGER, PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity from .helpers import async_get_turn_on_trigger @@ -49,11 +47,11 @@ def _inverted(data): async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: PhilipsTVConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the configuration entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = config_entry.runtime_data async_add_entities( [ PhilipsTVMediaPlayer( diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index 5972724c54b..ed63c7ce68d 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -12,24 +12,22 @@ from homeassistant.components.remote import ( DEFAULT_DELAY_SECS, RemoteEntity, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.trigger import PluggableAction -from . import LOGGER, PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import LOGGER, PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity from .helpers import async_get_turn_on_trigger async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: PhilipsTVConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the configuration entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = config_entry.runtime_data async_add_entities([PhilipsTVRemote(coordinator)]) diff --git a/homeassistant/components/philips_js/switch.py b/homeassistant/components/philips_js/switch.py index 697e7f2f060..93c4af24d98 100644 --- a/homeassistant/components/philips_js/switch.py +++ b/homeassistant/components/philips_js/switch.py @@ -5,12 +5,10 @@ from __future__ import annotations from typing import Any from homeassistant.components.switch import SwitchEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import PhilipsTVDataUpdateCoordinator -from .const import DOMAIN +from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity HUE_POWER_OFF = "Off" @@ -19,13 +17,11 @@ HUE_POWER_ON = "On" async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: PhilipsTVConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the configuration entry.""" - coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][ - config_entry.entry_id - ] + coordinator = config_entry.runtime_data async_add_entities([PhilipsTVScreenSwitch(coordinator)]) diff --git a/tests/components/philips_js/snapshots/test_diagnostics.ambr b/tests/components/philips_js/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..5cff47c7d62 --- /dev/null +++ b/tests/components/philips_js/snapshots/test_diagnostics.ambr @@ -0,0 +1,100 @@ +# serializer version: 1 +# name: test_entry_diagnostics + dict({ + 'data': dict({ + 'ambilight_cached': dict({ + }), + 'ambilight_current_configuration': None, + 'ambilight_measured': None, + 'ambilight_mode_raw': 'internal', + 'ambilight_modes': list([ + 'internal', + 'manual', + 'expert', + 'lounge', + ]), + 'ambilight_power': 'On', + 'ambilight_power_raw': dict({ + 'power': 'On', + }), + 'ambilight_processed': None, + 'ambilight_styles': dict({ + }), + 'ambilight_topology': None, + 'application': None, + 'applications': dict({ + }), + 'channel': None, + 'channel_lists': dict({ + 'all': dict({ + 'Channel': list([ + ]), + 'id': 'all', + 'installCountry': 'Poland', + 'listType': 'MixedSources', + 'medium': 'mixed', + 'operator': 'None', + 'version': 2, + }), + }), + 'channels': dict({ + }), + 'context': dict({ + 'data': 'NA', + 'level1': 'NA', + 'level2': 'NA', + 'level3': 'NA', + }), + 'favorite_lists': dict({ + '1': dict({ + 'channels': list([ + ]), + 'id': '1', + 'medium': 'mixed', + 'name': 'Favourites 1', + 'type': 'MixedSources', + 'version': '60', + }), + }), + 'on': True, + 'powerstate': None, + 'screenstate': 'On', + 'source_id': None, + 'sources': dict({ + }), + 'system': dict({ + 'country': 'Sweden', + 'menulanguage': 'English', + 'model': 'modelname', + 'name': 'Philips TV', + 'serialnumber': '**REDACTED**', + 'softwareversion': 'abcd', + }), + }), + 'entry': dict({ + 'data': dict({ + 'api_version': 1, + 'host': '1.1.1.1', + 'system': dict({ + 'country': 'Sweden', + 'menulanguage': 'English', + 'model': 'modelname', + 'name': 'Philips TV', + 'serialnumber': '**REDACTED**', + 'softwareversion': 'abcd', + }), + }), + 'disabled_by': None, + 'domain': 'philips_js', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': '**REDACTED**', + 'unique_id': '**REDACTED**', + 'version': 1, + }), + }) +# --- diff --git a/tests/components/philips_js/test_diagnostics.py b/tests/components/philips_js/test_diagnostics.py new file mode 100644 index 00000000000..cb3235b9780 --- /dev/null +++ b/tests/components/philips_js/test_diagnostics.py @@ -0,0 +1,66 @@ +"""Test the Philips TV diagnostics platform.""" + +from unittest.mock import AsyncMock + +from haphilipsjs.typing import ChannelListType, ContextType, FavoriteListType +from syrupy import SnapshotAssertion +from syrupy.filters import props + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + +TV_CONTEXT = ContextType(level1="NA", level2="NA", level3="NA", data="NA") +TV_CHANNEL_LISTS = { + "all": ChannelListType( + version=2, + id="all", + listType="MixedSources", + medium="mixed", + operator="None", + installCountry="Poland", + Channel=[], + ) +} +TV_FAVORITE_LISTS = { + "1": FavoriteListType( + version="60", + id="1", + type="MixedSources", + medium="mixed", + name="Favourites 1", + channels=[], + ) +} + + +async def test_entry_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_config_entry: MockConfigEntry, + mock_tv: AsyncMock, +) -> None: + """Test config entry diagnostics.""" + mock_tv.context = TV_CONTEXT + mock_tv.ambilight_topology = None + mock_tv.ambilight_mode_raw = "internal" + mock_tv.ambilight_modes = ["internal", "manual", "expert", "lounge"] + mock_tv.ambilight_power_raw = {"power": "On"} + mock_tv.ambilight_power = "On" + mock_tv.ambilight_measured = None + mock_tv.ambilight_processed = None + mock_tv.screenstate = "On" + mock_tv.channel = None + mock_tv.channel_lists = TV_CHANNEL_LISTS + mock_tv.favorite_lists = TV_FAVORITE_LISTS + + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + result = await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) + + assert result == snapshot(exclude=props("entry_id"))