From c66a892233042b5c1a721fd932008e6327e95f76 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 29 Jan 2021 00:05:00 -0800 Subject: [PATCH] Remove YAML support from Hyperion integration (#45690) --- homeassistant/components/hyperion/__init__.py | 4 - .../components/hyperion/config_flow.py | 8 - homeassistant/components/hyperion/light.py | 158 +---------------- tests/components/hyperion/__init__.py | 3 - tests/components/hyperion/test_config_flow.py | 57 +------ tests/components/hyperion/test_light.py | 159 +----------------- 6 files changed, 6 insertions(+), 383 deletions(-) diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index aeac922826d..b3606880f8c 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -278,10 +278,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b } ) - # Must only listen for option updates after the setup is complete, as otherwise - # the YAML->ConfigEntry migration code triggers an options update, which causes a - # reload -- which clashes with the initial load (causing entity_id / unique_id - # clashes). async def setup_then_listen() -> None: await asyncio.gather( *[ diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 11ab3289d14..f4528b0efbe 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -140,14 +140,6 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_auth() return await self.async_step_confirm() - async def async_step_import(self, import_data: ConfigType) -> Dict[str, Any]: - """Handle a flow initiated by a YAML config import.""" - self._data.update(import_data) - async with self._create_client(raw_connection=True) as hyperion_client: - if not hyperion_client: - return self.async_abort(reason="cannot_connect") - return await self._advance_to_auth_step_if_necessary(hyperion_client) - async def async_step_reauth( self, config_data: ConfigType, diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index a329ee5c20e..ce672194b9a 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -2,47 +2,30 @@ from __future__ import annotations import logging -import re from types import MappingProxyType from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple from hyperion import client, const -import voluptuous as vol -from homeassistant import data_entry_flow from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, - DOMAIN as LIGHT_DOMAIN, - PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, LightEntity, ) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback -from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) -from homeassistant.helpers.entity_registry import async_get_registry -from homeassistant.helpers.typing import ( - ConfigType, - DiscoveryInfoType, - HomeAssistantType, -) +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util -from . import ( - create_hyperion_client, - get_hyperion_unique_id, - listen_for_instance_updates, -) +from . import get_hyperion_unique_id, listen_for_instance_updates from .const import ( CONF_INSTANCE_CLIENTS, CONF_PRIORITY, @@ -73,8 +56,6 @@ CONF_EFFECT_LIST = "effect_list" # showing a solid color. This is the same method used by WLED. KEY_EFFECT_SOLID = "Solid" -KEY_ENTRY_ID_YAML = "YAML" - DEFAULT_COLOR = [255, 255, 255] DEFAULT_BRIGHTNESS = 255 DEFAULT_EFFECT = KEY_EFFECT_SOLID @@ -85,144 +66,11 @@ DEFAULT_EFFECT_LIST: List[str] = [] SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT -# Usage of YAML for configuration of the Hyperion component is deprecated. -PLATFORM_SCHEMA = vol.All( - cv.deprecated(CONF_HDMI_PRIORITY), - cv.deprecated(CONF_HOST), - cv.deprecated(CONF_PORT), - cv.deprecated(CONF_DEFAULT_COLOR), - cv.deprecated(CONF_NAME), - cv.deprecated(CONF_PRIORITY), - cv.deprecated(CONF_EFFECT_LIST), - PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All( - list, - vol.Length(min=3, max=3), - [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))], - ), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int, - vol.Optional( - CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY - ): cv.positive_int, - vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): vol.All( - cv.ensure_list, [cv.string] - ), - } - ), -) - ICON_LIGHTBULB = "mdi:lightbulb" ICON_EFFECT = "mdi:lava-lamp" ICON_EXTERNAL_SOURCE = "mdi:television-ambient-light" -async def async_setup_platform( - hass: HomeAssistantType, - config: ConfigType, - async_add_entities: Callable, - discovery_info: Optional[DiscoveryInfoType] = None, -) -> None: - """Set up Hyperion platform..""" - - # This is the entrypoint for the old YAML-style Hyperion integration. The goal here - # is to auto-convert the YAML configuration into a config entry, with no human - # interaction, preserving the entity_id. This should be possible, as the YAML - # configuration did not support any of the things that should otherwise require - # human interaction in the config flow (e.g. it did not support auth). - - host = config[CONF_HOST] - port = config[CONF_PORT] - instance = 0 # YAML only supports a single instance. - - # First, connect to the server and get the server id (which will be unique_id on a config_entry - # if there is one). - async with create_hyperion_client(host, port) as hyperion_client: - if not hyperion_client: - raise PlatformNotReady - hyperion_id = await hyperion_client.async_sysinfo_id() - if not hyperion_id: - raise PlatformNotReady - - future_unique_id = get_hyperion_unique_id( - hyperion_id, instance, TYPE_HYPERION_LIGHT - ) - - # Possibility 1: Already converted. - # There is already a config entry with the unique id reporting by the - # server. Nothing to do here. - for entry in hass.config_entries.async_entries(domain=DOMAIN): - if entry.unique_id == hyperion_id: - return - - # Possibility 2: Upgraded to the new Hyperion component pre-config-flow. - # No config entry for this unique_id, but have an entity_registry entry - # with an old-style unique_id: - # :- (instance will always be 0, as YAML - # configuration does not support multiple - # instances) - # The unique_id needs to be updated, then the config_flow should do the rest. - registry = await async_get_registry(hass) - for entity_id, entity in registry.entities.items(): - if entity.config_entry_id is not None or entity.platform != DOMAIN: - continue - result = re.search(rf"([^:]+):(\d+)-{instance}", entity.unique_id) - if result and result.group(1) == host and int(result.group(2)) == port: - registry.async_update_entity(entity_id, new_unique_id=future_unique_id) - break - else: - # Possibility 3: This is the first upgrade to the new Hyperion component. - # No config entry and no entity_registry entry, in which case the CONF_NAME - # variable will be used as the preferred name. Rather than pollute the config - # entry with a "suggested name" type variable, instead create an entry in the - # registry that will subsequently be used when the entity is created with this - # unique_id. - - # This also covers the case that should not occur in the wild (no config entry, - # but new style unique_id). - registry.async_get_or_create( - domain=LIGHT_DOMAIN, - platform=DOMAIN, - unique_id=future_unique_id, - suggested_object_id=config[CONF_NAME], - ) - - async def migrate_yaml_to_config_entry_and_options( - host: str, port: int, priority: int - ) -> None: - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_HOST: host, - CONF_PORT: port, - }, - ) - if ( - result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY - or result.get("result") is None - ): - _LOGGER.warning( - "Could not automatically migrate Hyperion YAML to a config entry." - ) - return - config_entry = result.get("result") - options = {**config_entry.options, CONF_PRIORITY: config[CONF_PRIORITY]} - hass.config_entries.async_update_entry(config_entry, options=options) - - _LOGGER.info( - "Successfully migrated Hyperion YAML configuration to a config entry." - ) - - # Kick off a config flow to create the config entry. - hass.async_create_task( - migrate_yaml_to_config_entry_and_options(host, port, config[CONF_PRIORITY]) - ) - - async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable ) -> bool: diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index e427cf46a83..f3b2ad383bd 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -9,7 +9,6 @@ from unittest.mock import AsyncMock, Mock, patch from hyperion import const from homeassistant.components.hyperion.const import CONF_PRIORITY, DOMAIN -from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.helpers.typing import HomeAssistantType @@ -24,8 +23,6 @@ TEST_ID = "default" TEST_SYSINFO_ID = "f9aab089-f85a-55cf-b7c1-222a72faebe9" TEST_SYSINFO_VERSION = "2.0.0-alpha.8" TEST_PRIORITY = 180 -TEST_YAML_NAME = f"{TEST_HOST}_{TEST_PORT}_{TEST_INSTANCE}" -TEST_YAML_ENTITY_ID = f"{LIGHT_DOMAIN}.{TEST_YAML_NAME}" TEST_ENTITY_ID_1 = "light.test_instance_1" TEST_ENTITY_ID_2 = "light.test_instance_2" TEST_ENTITY_ID_3 = "light.test_instance_3" diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index 776d5b3b25b..ef7046660d6 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -14,12 +14,7 @@ from homeassistant.components.hyperion.const import ( DOMAIN, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN -from homeassistant.config_entries import ( - SOURCE_IMPORT, - SOURCE_REAUTH, - SOURCE_SSDP, - SOURCE_USER, -) +from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_SSDP, SOURCE_USER from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, @@ -606,56 +601,6 @@ async def test_ssdp_abort_duplicates(hass: HomeAssistantType) -> None: assert result_2["reason"] == "already_in_progress" -async def test_import_success(hass: HomeAssistantType) -> None: - """Check an import flow from the old-style YAML.""" - - client = create_mock_client() - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ): - result = await _init_flow( - hass, - source=SOURCE_IMPORT, - data={ - CONF_HOST: TEST_HOST, - CONF_PORT: TEST_PORT, - }, - ) - await hass.async_block_till_done() - - # No human interaction should be required. - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["handler"] == DOMAIN - assert result["title"] == TEST_TITLE - assert result["data"] == { - CONF_HOST: TEST_HOST, - CONF_PORT: TEST_PORT, - } - - -async def test_import_cannot_connect(hass: HomeAssistantType) -> None: - """Check an import flow that cannot connect.""" - - client = create_mock_client() - client.async_client_connect = AsyncMock(return_value=False) - - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ): - result = await _init_flow( - hass, - source=SOURCE_IMPORT, - data={ - CONF_HOST: TEST_HOST, - CONF_PORT: TEST_PORT, - }, - ) - await hass.async_block_till_done() - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "cannot_connect" - - async def test_options(hass: HomeAssistantType) -> None: """Check an options flow.""" diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 7559af4d3c7..c3226cdd389 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1,21 +1,12 @@ """Tests for the Hyperion integration.""" import logging -from types import MappingProxyType from typing import Optional from unittest.mock import AsyncMock, Mock, call, patch from hyperion import const -from homeassistant import setup -from homeassistant.components.hyperion import ( - get_hyperion_unique_id, - light as hyperion_light, -) -from homeassistant.components.hyperion.const import ( - DEFAULT_ORIGIN, - DOMAIN, - TYPE_HYPERION_LIGHT, -) +from homeassistant.components.hyperion import light as hyperion_light +from homeassistant.components.hyperion.const import DEFAULT_ORIGIN, DOMAIN from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, @@ -43,7 +34,6 @@ import homeassistant.util.color as color_util from . import ( TEST_AUTH_NOT_REQUIRED_RESP, TEST_AUTH_REQUIRED_RESP, - TEST_CONFIG_ENTRY_OPTIONS, TEST_ENTITY_ID_1, TEST_ENTITY_ID_2, TEST_ENTITY_ID_3, @@ -56,8 +46,6 @@ from . import ( TEST_PRIORITY, TEST_PRIORITY_LIGHT_ENTITY_ID_1, TEST_SYSINFO_ID, - TEST_YAML_ENTITY_ID, - TEST_YAML_NAME, add_test_config_entry, call_registered_callback, create_mock_client, @@ -69,28 +57,6 @@ _LOGGER = logging.getLogger(__name__) COLOR_BLACK = color_util.COLORS["black"] -async def _setup_entity_yaml(hass: HomeAssistantType, client: AsyncMock = None) -> None: - """Add a test Hyperion entity to hass.""" - client = client or create_mock_client() - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ): - assert await setup.async_setup_component( - hass, - LIGHT_DOMAIN, - { - LIGHT_DOMAIN: { - "platform": "hyperion", - "name": TEST_YAML_NAME, - "host": TEST_HOST, - "port": TEST_PORT, - "priority": TEST_PRIORITY, - } - }, - ) - await hass.async_block_till_done() - - def _get_config_entry_from_unique_id( hass: HomeAssistantType, unique_id: str ) -> Optional[ConfigEntry]: @@ -100,127 +66,6 @@ def _get_config_entry_from_unique_id( return None -async def test_setup_yaml_already_converted(hass: HomeAssistantType) -> None: - """Test an already converted YAML style config.""" - # This tests "Possibility 1" from async_setup_platform() - - # Add a pre-existing config entry. - add_test_config_entry(hass) - client = create_mock_client() - await _setup_entity_yaml(hass, client=client) - assert client.async_client_disconnect.called - - # Setup should be skipped for the YAML config as there is a pre-existing config - # entry. - assert hass.states.get(TEST_YAML_ENTITY_ID) is None - - -async def test_setup_yaml_old_style_unique_id(hass: HomeAssistantType) -> None: - """Test an already converted YAML style config.""" - # This tests "Possibility 2" from async_setup_platform() - old_unique_id = f"{TEST_HOST}:{TEST_PORT}-0" - - # Add a pre-existing registry entry. - registry = await async_get_registry(hass) - registry.async_get_or_create( - domain=LIGHT_DOMAIN, - platform=DOMAIN, - unique_id=old_unique_id, - suggested_object_id=TEST_YAML_NAME, - ) - - client = create_mock_client() - await _setup_entity_yaml(hass, client=client) - assert client.async_client_disconnect.called - - # The entity should have been created with the same entity_id. - assert hass.states.get(TEST_YAML_ENTITY_ID) is not None - - # The unique_id should have been updated in the registry (rather than the one - # specified above). - assert registry.async_get(TEST_YAML_ENTITY_ID).unique_id == get_hyperion_unique_id( - TEST_SYSINFO_ID, 0, TYPE_HYPERION_LIGHT - ) - assert registry.async_get_entity_id(LIGHT_DOMAIN, DOMAIN, old_unique_id) is None - - # There should be a config entry with the correct server unique_id. - entry = _get_config_entry_from_unique_id(hass, TEST_SYSINFO_ID) - assert entry - assert entry.options == MappingProxyType(TEST_CONFIG_ENTRY_OPTIONS) - - -async def test_setup_yaml_new_style_unique_id_wo_config( - hass: HomeAssistantType, -) -> None: - """Test an a new unique_id without a config entry.""" - # Note: This casde should not happen in the wild, as no released version of Home - # Assistant should this combination, but verify correct behavior for defense in - # depth. - - new_unique_id = get_hyperion_unique_id(TEST_SYSINFO_ID, 0, TYPE_HYPERION_LIGHT) - entity_id_to_preserve = "light.magic_entity" - - # Add a pre-existing registry entry. - registry = await async_get_registry(hass) - registry.async_get_or_create( - domain=LIGHT_DOMAIN, - platform=DOMAIN, - unique_id=new_unique_id, - suggested_object_id=entity_id_to_preserve.split(".")[1], - ) - - client = create_mock_client() - await _setup_entity_yaml(hass, client=client) - assert client.async_client_disconnect.called - - # The entity should have been created with the same entity_id. - assert hass.states.get(entity_id_to_preserve) is not None - - # The unique_id should have been updated in the registry (rather than the one - # specified above). - assert registry.async_get(entity_id_to_preserve).unique_id == new_unique_id - - # There should be a config entry with the correct server unique_id. - entry = _get_config_entry_from_unique_id(hass, TEST_SYSINFO_ID) - assert entry - assert entry.options == MappingProxyType(TEST_CONFIG_ENTRY_OPTIONS) - - -async def test_setup_yaml_no_registry_entity(hass: HomeAssistantType) -> None: - """Test an already converted YAML style config.""" - # This tests "Possibility 3" from async_setup_platform() - - registry = await async_get_registry(hass) - - # Add a pre-existing config entry. - client = create_mock_client() - await _setup_entity_yaml(hass, client=client) - assert client.async_client_disconnect.called - - # The entity should have been created with the same entity_id. - assert hass.states.get(TEST_YAML_ENTITY_ID) is not None - - # The unique_id should have been updated in the registry (rather than the one - # specified above). - assert registry.async_get(TEST_YAML_ENTITY_ID).unique_id == get_hyperion_unique_id( - TEST_SYSINFO_ID, 0, TYPE_HYPERION_LIGHT - ) - - # There should be a config entry with the correct server unique_id. - entry = _get_config_entry_from_unique_id(hass, TEST_SYSINFO_ID) - assert entry - assert entry.options == MappingProxyType(TEST_CONFIG_ENTRY_OPTIONS) - - -async def test_setup_yaml_not_ready(hass: HomeAssistantType) -> None: - """Test the component not being ready.""" - client = create_mock_client() - client.async_client_connect = AsyncMock(return_value=False) - await _setup_entity_yaml(hass, client=client) - assert client.async_client_disconnect.called - assert hass.states.get(TEST_YAML_ENTITY_ID) is None - - async def test_setup_config_entry(hass: HomeAssistantType) -> None: """Test setting up the component via config entries.""" await setup_test_config_entry(hass, hyperion_client=create_mock_client())