From 4a921ac67fbd200ea6c8cd32af13f6e55a7835e3 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 1 Apr 2022 11:05:59 +0300 Subject: [PATCH] Remove webostv deprecated YAML import (#69043) * webostv: remove deprecated YAML import * Remove unused CUSTOMIZE_SCHEMA and WEBOSTV_CONFIG_FILE --- homeassistant/components/webostv/__init__.py | 145 +----------------- .../components/webostv/config_flow.py | 30 +--- homeassistant/components/webostv/const.py | 2 - .../components/webostv/manifest.json | 2 +- requirements_all.txt | 1 - requirements_test_all.txt | 1 - tests/components/webostv/__init__.py | 55 +------ tests/components/webostv/test_config_flow.py | 61 -------- tests/components/webostv/test_init.py | 141 ----------------- 9 files changed, 8 insertions(+), 430 deletions(-) delete mode 100644 tests/components/webostv/test_init.py diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 9f78dcd0964..e759077ad3e 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,33 +1,24 @@ """Support for LG webOS Smart TV.""" from __future__ import annotations -import asyncio from collections.abc import Callable from contextlib import suppress -import json import logging -import os -from pickle import loads from typing import Any from aiowebostv import WebOsClient, WebOsTvPairError -import sqlalchemy as db import voluptuous as vol from homeassistant.components import notify as hass_notify from homeassistant.components.automation import AutomationActionType -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_COMMAND, ATTR_ENTITY_ID, CONF_CLIENT_SECRET, - CONF_CUSTOMIZE, CONF_HOST, - CONF_ICON, CONF_NAME, - CONF_UNIQUE_ID, EVENT_HOMEASSISTANT_STOP, - Platform, ) from homeassistant.core import ( Context, @@ -37,7 +28,7 @@ from homeassistant.core import ( ServiceCall, callback, ) -from homeassistant.helpers import config_validation as cv, discovery, entity_registry +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType @@ -46,46 +37,17 @@ from .const import ( ATTR_CONFIG_ENTRY_ID, ATTR_PAYLOAD, ATTR_SOUND_OUTPUT, - CONF_ON_ACTION, - CONF_SOURCES, DATA_CONFIG_ENTRY, DATA_HASS_CONFIG, - DEFAULT_NAME, DOMAIN, PLATFORMS, SERVICE_BUTTON, SERVICE_COMMAND, SERVICE_SELECT_SOUND_OUTPUT, - WEBOSTV_CONFIG_FILE, WEBOSTV_EXCEPTIONS, ) -CUSTOMIZE_SCHEMA = vol.Schema( - {vol.Optional(CONF_SOURCES, default=[]): vol.All(cv.ensure_list, [cv.string])} -) - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.All( - cv.ensure_list, - [ - vol.Schema( - { - vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_ICON): cv.string, - } - ) - ], - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) +CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids}) @@ -109,113 +71,12 @@ SERVICE_TO_METHOD = { _LOGGER = logging.getLogger(__name__) -def read_client_keys(config_file: str) -> dict[str, str]: - """Read legacy client keys from file.""" - if not os.path.isfile(config_file): - return {} - - # Try to parse the file as being JSON - with open(config_file, encoding="utf8") as json_file: - try: - client_keys = json.load(json_file) - if isinstance(client_keys, dict): - return client_keys - return {} - except (json.JSONDecodeError, UnicodeDecodeError): - pass - - # If the file is not JSON, read it as Sqlite DB - engine = db.create_engine(f"sqlite:///{config_file}") - table = db.Table("unnamed", db.MetaData(), autoload=True, autoload_with=engine) - results = engine.connect().execute(db.select([table])).fetchall() - db_client_keys = {k: loads(v) for k, v in results} - return db_client_keys - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the LG WebOS TV platform.""" hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN].setdefault(DATA_CONFIG_ENTRY, {}) hass.data[DOMAIN][DATA_HASS_CONFIG] = config - if DOMAIN not in config: - return True - - config_file = hass.config.path(WEBOSTV_CONFIG_FILE) - if not ( - client_keys := await hass.async_add_executor_job(read_client_keys, config_file) - ): - _LOGGER.debug("No pairing keys, Not importing webOS Smart TV YAML config") - return True - - async def async_migrate_task( - entity_id: str, conf: dict[str, str], key: str - ) -> None: - _LOGGER.debug("Migrating webOS Smart TV entity %s unique_id", entity_id) - client = WebOsClient(conf[CONF_HOST], key) - tries = 0 - while not client.is_connected(): - try: - await client.connect() - except WEBOSTV_EXCEPTIONS: - if tries == 0: - _LOGGER.warning( - "Please make sure webOS TV %s is turned on to complete " - "the migration of configuration.yaml to the UI", - entity_id, - ) - wait_time = 2 ** min(tries, 4) * 5 - tries += 1 - await asyncio.sleep(wait_time) - except WebOsTvPairError: - return - - ent_reg = entity_registry.async_get(hass) - if not ( - new_entity_id := ent_reg.async_get_entity_id( - Platform.MEDIA_PLAYER, DOMAIN, key - ) - ): - _LOGGER.debug( - "Not updating webOSTV Smart TV entity %s unique_id, entity missing", - entity_id, - ) - return - - uuid = client.hello_info["deviceUUID"] - ent_reg.async_update_entity(new_entity_id, new_unique_id=uuid) - await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - **conf, - CONF_CLIENT_SECRET: key, - CONF_UNIQUE_ID: uuid, - }, - ) - - ent_reg = entity_registry.async_get(hass) - - tasks = [] - for conf in config[DOMAIN]: - host = conf[CONF_HOST] - if (key := client_keys.get(host)) is None: - _LOGGER.debug( - "Not importing webOS Smart TV host %s without pairing key", host - ) - continue - - if entity_id := ent_reg.async_get_entity_id(Platform.MEDIA_PLAYER, DOMAIN, key): - tasks.append(asyncio.create_task(async_migrate_task(entity_id, conf, key))) - - async def async_tasks_cancel(_event: Event) -> None: - """Cancel config flow import tasks.""" - for task in tasks: - if not task.done(): - task.cancel() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_tasks_cancel) - return True diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 18338e86f1a..85da9250539 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -10,13 +10,7 @@ import voluptuous as vol from homeassistant import config_entries, data_entry_flow from homeassistant.components import ssdp -from homeassistant.const import ( - CONF_CLIENT_SECRET, - CONF_CUSTOMIZE, - CONF_HOST, - CONF_NAME, - CONF_UNIQUE_ID, -) +from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST, CONF_NAME from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv @@ -55,28 +49,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) - async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: - """Set the config entry up from yaml.""" - self._host = import_info[CONF_HOST] - self._name = import_info.get(CONF_NAME) or import_info[CONF_HOST] - await self.async_set_unique_id( - import_info[CONF_UNIQUE_ID], raise_on_progress=False - ) - data = { - CONF_HOST: self._host, - CONF_CLIENT_SECRET: import_info[CONF_CLIENT_SECRET], - } - self._abort_if_unique_id_configured() - - options: dict[str, list[str]] | None = None - if sources := import_info.get(CONF_CUSTOMIZE, {}).get(CONF_SOURCES): - if not isinstance(sources, list): - sources = [s.strip() for s in sources.split(",")] - options = {CONF_SOURCES: sources} - - _LOGGER.debug("WebOS Smart TV host %s imported from YAML config", self._host) - return self.async_create_entry(title=self._name, data=data, options=options) - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: diff --git a/homeassistant/components/webostv/const.py b/homeassistant/components/webostv/const.py index 9be44d86469..f471ca7340d 100644 --- a/homeassistant/components/webostv/const.py +++ b/homeassistant/components/webostv/const.py @@ -33,5 +33,3 @@ WEBOSTV_EXCEPTIONS = ( asyncio.TimeoutError, asyncio.CancelledError, ) - -WEBOSTV_CONFIG_FILE = "webostv.conf" diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index 2725609f119..81c4d04901f 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -3,7 +3,7 @@ "name": "LG webOS Smart TV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiowebostv==0.2.0", "sqlalchemy==1.4.32"], + "requirements": ["aiowebostv==0.2.0"], "codeowners": ["@bendavid", "@thecode"], "ssdp": [{ "st": "urn:lge-com:service:webos-second-screen:1" }], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index 4953f1bc161..6d5884d725f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2194,7 +2194,6 @@ spotipy==2.19.0 # homeassistant.components.recorder # homeassistant.components.sql -# homeassistant.components.webostv sqlalchemy==1.4.32 # homeassistant.components.srp_energy diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de51f1611b3..b0b0f7ffcf9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1419,7 +1419,6 @@ spotipy==2.19.0 # homeassistant.components.recorder # homeassistant.components.sql -# homeassistant.components.webostv sqlalchemy==1.4.32 # homeassistant.components.srp_energy diff --git a/tests/components/webostv/__init__.py b/tests/components/webostv/__init__.py index 1cbc72b43fc..5ef210da56d 100644 --- a/tests/components/webostv/__init__.py +++ b/tests/components/webostv/__init__.py @@ -1,17 +1,10 @@ """Tests for the WebOS TV integration.""" -from pickle import dumps -from unittest.mock import patch -import sqlalchemy as db -from sqlalchemy import create_engine - -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.webostv.const import DOMAIN from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST -from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component -from .const import CLIENT_KEY, FAKE_UUID, HOST, MOCK_CLIENT_KEYS, TV_NAME +from .const import CLIENT_KEY, FAKE_UUID, HOST, TV_NAME from tests.common import MockConfigEntry @@ -29,53 +22,11 @@ async def setup_webostv(hass, unique_id=FAKE_UUID): ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.webostv.read_client_keys", - return_value=MOCK_CLIENT_KEYS, - ): - await async_setup_component( - hass, - DOMAIN, - {DOMAIN: {CONF_HOST: HOST}}, - ) - await hass.async_block_till_done() - - return entry - - -async def setup_legacy_component(hass, create_entity=True): - """Initialize webostv component with legacy entity.""" - if create_entity: - ent_reg = entity_registry.async_get(hass) - assert ent_reg.async_get_or_create(MP_DOMAIN, DOMAIN, CLIENT_KEY) - - assert await async_setup_component( + await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_HOST: HOST}}, ) await hass.async_block_till_done() - -def create_memory_sqlite_engine(url): - """Create fake db keys file in memory.""" - mem_eng = create_engine("sqlite:///:memory:") - table = db.Table( - "unnamed", - db.MetaData(), - db.Column("key", db.String), - db.Column("value", db.String), - ) - table.create(mem_eng) - query = db.insert(table).values(key=HOST, value=dumps(CLIENT_KEY)) - connection = mem_eng.connect() - connection.execute(query) - return mem_eng - - -def is_entity_unique_id_updated(hass): - """Check if entity has new unique_id from UUID.""" - ent_reg = entity_registry.async_get(hass) - return ent_reg.async_get_entity_id( - MP_DOMAIN, DOMAIN, FAKE_UUID - ) and not ent_reg.async_get_entity_id(MP_DOMAIN, DOMAIN, CLIENT_KEY) + return entry diff --git a/tests/components/webostv/test_config_flow.py b/tests/components/webostv/test_config_flow.py index a874532ff51..012682615dc 100644 --- a/tests/components/webostv/test_config_flow.py +++ b/tests/components/webostv/test_config_flow.py @@ -11,7 +11,6 @@ from homeassistant.components.webostv.const import CONF_SOURCES, DOMAIN, LIVE_TV from homeassistant.config_entries import SOURCE_SSDP from homeassistant.const import ( CONF_CLIENT_SECRET, - CONF_CUSTOMIZE, CONF_HOST, CONF_ICON, CONF_NAME, @@ -46,66 +45,6 @@ MOCK_DISCOVERY_INFO = ssdp.SsdpServiceInfo( ) -async def test_import(hass, client): - """Test we can import yaml config.""" - assert client - - with patch("homeassistant.components.webostv.async_setup_entry", return_value=True): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: config_entries.SOURCE_IMPORT}, - data=MOCK_YAML_CONFIG, - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == TV_NAME - assert result["data"][CONF_HOST] == MOCK_YAML_CONFIG[CONF_HOST] - assert result["data"][CONF_CLIENT_SECRET] == MOCK_YAML_CONFIG[CONF_CLIENT_SECRET] - assert result["result"].unique_id == MOCK_YAML_CONFIG[CONF_UNIQUE_ID] - - with patch("homeassistant.components.webostv.async_setup_entry", return_value=True): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: config_entries.SOURCE_IMPORT}, - data=MOCK_YAML_CONFIG, - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - -@pytest.mark.parametrize( - "sources", - [ - ["Live TV", "Input01", "Input02"], - "Live TV, Input01 , Input02", - "Live TV,Input01 ,Input02", - ], -) -async def test_import_sources(hass, client, sources): - """Test import yaml config with sources list/csv.""" - assert client - - with patch("homeassistant.components.webostv.async_setup_entry", return_value=True): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: config_entries.SOURCE_IMPORT}, - data={ - **MOCK_YAML_CONFIG, - CONF_CUSTOMIZE: { - CONF_SOURCES: sources, - }, - }, - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == TV_NAME - assert result["data"][CONF_HOST] == MOCK_YAML_CONFIG[CONF_HOST] - assert result["data"][CONF_CLIENT_SECRET] == MOCK_YAML_CONFIG[CONF_CLIENT_SECRET] - assert result["options"][CONF_SOURCES] == ["Live TV", "Input01", "Input02"] - assert result["result"].unique_id == MOCK_YAML_CONFIG[CONF_UNIQUE_ID] - - async def test_form(hass, client): """Test we get the form.""" assert client diff --git a/tests/components/webostv/test_init.py b/tests/components/webostv/test_init.py deleted file mode 100644 index 8729576d869..00000000000 --- a/tests/components/webostv/test_init.py +++ /dev/null @@ -1,141 +0,0 @@ -"""The tests for the WebOS TV platform.""" - -from unittest.mock import Mock, mock_open, patch - -from aiowebostv import WebOsTvPairError - -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN -from homeassistant.components.webostv import DOMAIN - -from . import ( - create_memory_sqlite_engine, - is_entity_unique_id_updated, - setup_legacy_component, -) -from .const import MOCK_JSON - - -async def test_missing_keys_file_abort(hass, client, caplog): - """Test abort import when no pairing keys file.""" - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=False) - ): - await setup_legacy_component(hass) - - assert "No pairing keys, Not importing" in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_empty_json_abort(hass, client, caplog): - """Test abort import when keys file is empty.""" - m_open = mock_open(read_data="[]") - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert "No pairing keys, Not importing" in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_valid_json_migrate_not_needed(hass, client, caplog): - """Test import from valid json entity already migrated or removed.""" - m_open = mock_open(read_data=MOCK_JSON) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass, False) - - assert "Migrating webOS Smart TV entity" not in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_valid_json_missing_host_key(hass, client, caplog): - """Test import from valid json missing host key.""" - m_open = mock_open(read_data='{"1.2.3.5": "other-key"}') - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert "Not importing webOS Smart TV host" in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_not_connected_import(hass, client, caplog, monkeypatch): - """Test import while device is not connected.""" - m_open = mock_open(read_data=MOCK_JSON) - monkeypatch.setattr(client, "is_connected", Mock(return_value=False)) - monkeypatch.setattr(client, "connect", Mock(side_effect=OSError)) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert f"Please make sure webOS TV {MP_DOMAIN}.{DOMAIN}" in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_pair_error_import_abort(hass, client, caplog, monkeypatch): - """Test abort import if device is not paired.""" - m_open = mock_open(read_data=MOCK_JSON) - monkeypatch.setattr(client, "is_connected", Mock(return_value=False)) - monkeypatch.setattr(client, "connect", Mock(side_effect=WebOsTvPairError)) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert f"Please make sure webOS TV {MP_DOMAIN}.{DOMAIN}" not in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_entity_removed_import_abort(hass, client_entity_removed, caplog): - """Test abort import if entity removed by user during import.""" - m_open = mock_open(read_data=MOCK_JSON) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert "Not updating webOSTV Smart TV entity" in caplog.text - assert not is_entity_unique_id_updated(hass) - - -async def test_json_import(hass, client, caplog, monkeypatch): - """Test import from json keys file.""" - m_open = mock_open(read_data=MOCK_JSON) - monkeypatch.setattr(client, "is_connected", Mock(return_value=True)) - monkeypatch.setattr(client, "connect", Mock(return_value=True)) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True): - await setup_legacy_component(hass) - - assert "imported from YAML config" in caplog.text - assert is_entity_unique_id_updated(hass) - - -async def test_sqlite_import(hass, client, caplog, monkeypatch): - """Test import from sqlite keys file.""" - m_open = mock_open(read_data="will raise JSONDecodeError") - monkeypatch.setattr(client, "is_connected", Mock(return_value=True)) - monkeypatch.setattr(client, "connect", Mock(return_value=True)) - - with patch( - "homeassistant.components.webostv.os.path.isfile", Mock(return_value=True) - ), patch("homeassistant.components.webostv.open", m_open, create=True), patch( - "homeassistant.components.webostv.db.create_engine", - side_effect=create_memory_sqlite_engine, - ): - await setup_legacy_component(hass) - - assert "imported from YAML config" in caplog.text - assert is_entity_unique_id_updated(hass)