From 640f53ce21b26ca42989100273612945fd744bf5 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 30 May 2022 17:07:18 +0200 Subject: [PATCH] Remove YAML configuration from upnp (#72410) --- homeassistant/components/upnp/__init__.py | 42 +-------- homeassistant/components/upnp/config_flow.py | 87 +----------------- tests/components/upnp/conftest.py | 5 +- tests/components/upnp/test_config_flow.py | 96 +------------------- 4 files changed, 11 insertions(+), 219 deletions(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 07560f7413f..27d69f9c509 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -5,13 +5,10 @@ import asyncio from collections.abc import Mapping from dataclasses import dataclass from datetime import timedelta -from ipaddress import ip_address from typing import Any from async_upnp_client.exceptions import UpnpCommunicationError, UpnpConnectionError -import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.binary_sensor import BinarySensorEntityDescription from homeassistant.components.sensor import SensorEntityDescription @@ -19,10 +16,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -30,7 +25,6 @@ from homeassistant.helpers.update_coordinator import ( ) from .const import ( - CONF_LOCAL_IP, CONFIG_ENTRY_MAC_ADDRESS, CONFIG_ENTRY_ORIGINAL_UDN, CONFIG_ENTRY_ST, @@ -46,43 +40,15 @@ NOTIFICATION_TITLE = "UPnP/IGD Setup" PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.Schema( - vol.All( - cv.deprecated(CONF_LOCAL_IP), - { - vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string), - }, - ) - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up UPnP component.""" - hass.data[DOMAIN] = {} - - # Only start if set up via configuration.yaml. - if DOMAIN in config: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} - ) - ) - - return True +CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up UPnP/IGD device from a config entry.""" LOGGER.debug("Setting up config entry: %s", entry.entry_id) + hass.data.setdefault(DOMAIN, {}) + udn = entry.data[CONFIG_ENTRY_UDN] st = entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name usn = f"{udn}::{st}" diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index b54098b6566..7fa37f589bf 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,7 +1,6 @@ """Config flow for UPNP.""" from __future__ import annotations -import asyncio from collections.abc import Mapping from typing import Any, cast @@ -9,7 +8,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.components.ssdp import SsdpChange, SsdpServiceInfo +from homeassistant.components.ssdp import SsdpServiceInfo from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult @@ -21,7 +20,6 @@ from .const import ( CONFIG_ENTRY_UDN, DOMAIN, LOGGER, - SSDP_SEARCH_TIMEOUT, ST_IGD_V1, ST_IGD_V2, ) @@ -48,47 +46,6 @@ def _is_complete_discovery(discovery_info: ssdp.SsdpServiceInfo) -> bool: ) -async def _async_wait_for_discoveries(hass: HomeAssistant) -> bool: - """Wait for a device to be discovered.""" - device_discovered_event = asyncio.Event() - - async def device_discovered(info: SsdpServiceInfo, change: SsdpChange) -> None: - if change != SsdpChange.BYEBYE: - LOGGER.debug( - "Device discovered: %s, at: %s", - info.ssdp_usn, - info.ssdp_location, - ) - device_discovered_event.set() - - cancel_discovered_callback_1 = await ssdp.async_register_callback( - hass, - device_discovered, - { - ssdp.ATTR_SSDP_ST: ST_IGD_V1, - }, - ) - cancel_discovered_callback_2 = await ssdp.async_register_callback( - hass, - device_discovered, - { - ssdp.ATTR_SSDP_ST: ST_IGD_V2, - }, - ) - - try: - await asyncio.wait_for( - device_discovered_event.wait(), timeout=SSDP_SEARCH_TIMEOUT - ) - except asyncio.TimeoutError: - return False - finally: - cancel_discovered_callback_1() - cancel_discovered_callback_2() - - return True - - async def _async_discover_igd_devices( hass: HomeAssistant, ) -> list[ssdp.SsdpServiceInfo]: @@ -120,7 +77,9 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the UPnP/IGD config flow.""" self._discoveries: list[SsdpServiceInfo] | None = None - async def async_step_user(self, user_input: Mapping | None = None) -> FlowResult: + async def async_step_user( + self, user_input: Mapping[str, Any] | None = None + ) -> FlowResult: """Handle a flow start.""" LOGGER.debug("async_step_user: user_input: %s", user_input) @@ -172,42 +131,6 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=data_schema, ) - async def async_step_import(self, import_info: Mapping | None) -> Mapping[str, Any]: - """Import a new UPnP/IGD device as a config entry. - - This flow is triggered by `async_setup`. If no device has been - configured before, find any device and create a config_entry for it. - Otherwise, do nothing. - """ - LOGGER.debug("async_step_import: import_info: %s", import_info) - - # Landed here via configuration.yaml entry. - # Any device already added, then abort. - if self._async_current_entries(): - LOGGER.debug("Already configured, aborting") - return self.async_abort(reason="already_configured") - - # Discover devices. - await _async_wait_for_discoveries(self.hass) - discoveries = await _async_discover_igd_devices(self.hass) - - # Ensure anything to add. If not, silently abort. - if not discoveries: - LOGGER.info("No UPnP devices discovered, aborting") - return self.async_abort(reason="no_devices_found") - - # Ensure complete discovery. - discovery = discoveries[0] - if not _is_complete_discovery(discovery): - LOGGER.debug("Incomplete discovery, ignoring") - return self.async_abort(reason="incomplete_discovery") - - # Ensure not already configuring/configured. - unique_id = discovery.ssdp_usn - await self.async_set_unique_id(unique_id) - - return await self._async_create_entry_from_discovery(discovery) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: """Handle a discovered UPnP/IGD device. @@ -275,7 +198,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_ssdp_confirm() async def async_step_ssdp_confirm( - self, user_input: Mapping | None = None + self, user_input: Mapping[str, Any] | None = None ) -> FlowResult: """Confirm integration via SSDP.""" LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) diff --git a/tests/components/upnp/conftest.py b/tests/components/upnp/conftest.py index 687518bb46d..0d3e869db35 100644 --- a/tests/components/upnp/conftest.py +++ b/tests/components/upnp/conftest.py @@ -156,10 +156,7 @@ async def ssdp_no_discovery(): ) as mock_register, patch( "homeassistant.components.ssdp.async_get_discovery_info_by_st", return_value=[], - ) as mock_get_info, patch( - "homeassistant.components.upnp.config_flow.SSDP_SEARCH_TIMEOUT", - 0.1, - ): + ) as mock_get_info: yield (mock_register, mock_get_info) diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index ea8b3381cd1..80847ec2737 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -1,7 +1,7 @@ """Test UPnP/IGD config flow.""" from copy import deepcopy -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest @@ -341,97 +341,3 @@ async def test_flow_user_no_discovery(hass: HomeAssistant): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_devices_found" - - -@pytest.mark.usefixtures( - "ssdp_instant_discovery", - "mock_setup_entry", - "mock_get_source_ip", - "mock_mac_address_from_host", -) -async def test_flow_import(hass: HomeAssistant): - """Test config flow: configured through configuration.yaml.""" - # Discovered via step import. - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == TEST_FRIENDLY_NAME - assert result["data"] == { - CONFIG_ENTRY_ST: TEST_ST, - CONFIG_ENTRY_UDN: TEST_UDN, - CONFIG_ENTRY_ORIGINAL_UDN: TEST_UDN, - CONFIG_ENTRY_LOCATION: TEST_LOCATION, - CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, - } - - -@pytest.mark.usefixtures( - "mock_get_source_ip", -) -async def test_flow_import_incomplete_discovery(hass: HomeAssistant): - """Test config flow: configured through configuration.yaml, but incomplete discovery.""" - incomplete_discovery = ssdp.SsdpServiceInfo( - ssdp_usn=TEST_USN, - ssdp_st=TEST_ST, - ssdp_location=TEST_LOCATION, - upnp={ - # ssdp.ATTR_UPNP_UDN: TEST_UDN, # Not provided. - }, - ) - - async def register_callback(hass, callback, match_dict): - """Immediately do callback.""" - await callback(incomplete_discovery, ssdp.SsdpChange.ALIVE) - return MagicMock() - - with patch( - "homeassistant.components.ssdp.async_register_callback", - side_effect=register_callback, - ), patch( - "homeassistant.components.upnp.ssdp.async_get_discovery_info_by_st", - return_value=[incomplete_discovery], - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "incomplete_discovery" - - -@pytest.mark.usefixtures("ssdp_instant_discovery", "mock_get_source_ip") -async def test_flow_import_already_configured(hass: HomeAssistant): - """Test config flow: configured through configuration.yaml, but existing config entry.""" - # Existing entry. - entry = MockConfigEntry( - domain=DOMAIN, - unique_id=TEST_USN, - data={ - CONFIG_ENTRY_ST: TEST_ST, - CONFIG_ENTRY_UDN: TEST_UDN, - CONFIG_ENTRY_ORIGINAL_UDN: TEST_UDN, - CONFIG_ENTRY_LOCATION: TEST_LOCATION, - CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, - }, - state=config_entries.ConfigEntryState.LOADED, - ) - entry.add_to_hass(hass) - - # Discovered via step import. - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - -@pytest.mark.usefixtures("ssdp_no_discovery", "mock_get_source_ip") -async def test_flow_import_no_devices_found(hass: HomeAssistant): - """Test config flow: no devices found, configured through configuration.yaml.""" - # Discovered via step import. - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "no_devices_found"