Remove YAML configuration from upnp (#72410)

pull/72715/head
epenet 2022-05-30 17:07:18 +02:00 committed by GitHub
parent 57ed667257
commit 640f53ce21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 11 additions and 219 deletions

View File

@ -5,13 +5,10 @@ import asyncio
from collections.abc import Mapping from collections.abc import Mapping
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from ipaddress import ip_address
from typing import Any from typing import Any
from async_upnp_client.exceptions import UpnpCommunicationError, UpnpConnectionError 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 import ssdp
from homeassistant.components.binary_sensor import BinarySensorEntityDescription from homeassistant.components.binary_sensor import BinarySensorEntityDescription
from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.sensor import SensorEntityDescription
@ -19,10 +16,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import config_validation as cv, device_registry as dr
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
DataUpdateCoordinator, DataUpdateCoordinator,
@ -30,7 +25,6 @@ from homeassistant.helpers.update_coordinator import (
) )
from .const import ( from .const import (
CONF_LOCAL_IP,
CONFIG_ENTRY_MAC_ADDRESS, CONFIG_ENTRY_MAC_ADDRESS,
CONFIG_ENTRY_ORIGINAL_UDN, CONFIG_ENTRY_ORIGINAL_UDN,
CONFIG_ENTRY_ST, CONFIG_ENTRY_ST,
@ -46,43 +40,15 @@ NOTIFICATION_TITLE = "UPnP/IGD Setup"
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
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
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up UPnP/IGD device from a config entry.""" """Set up UPnP/IGD device from a config entry."""
LOGGER.debug("Setting up config entry: %s", entry.entry_id) LOGGER.debug("Setting up config entry: %s", entry.entry_id)
hass.data.setdefault(DOMAIN, {})
udn = entry.data[CONFIG_ENTRY_UDN] udn = entry.data[CONFIG_ENTRY_UDN]
st = entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name st = entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name
usn = f"{udn}::{st}" usn = f"{udn}::{st}"

View File

@ -1,7 +1,6 @@
"""Config flow for UPNP.""" """Config flow for UPNP."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any, cast from typing import Any, cast
@ -9,7 +8,7 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp 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.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
@ -21,7 +20,6 @@ from .const import (
CONFIG_ENTRY_UDN, CONFIG_ENTRY_UDN,
DOMAIN, DOMAIN,
LOGGER, LOGGER,
SSDP_SEARCH_TIMEOUT,
ST_IGD_V1, ST_IGD_V1,
ST_IGD_V2, 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( async def _async_discover_igd_devices(
hass: HomeAssistant, hass: HomeAssistant,
) -> list[ssdp.SsdpServiceInfo]: ) -> list[ssdp.SsdpServiceInfo]:
@ -120,7 +77,9 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Initialize the UPnP/IGD config flow.""" """Initialize the UPnP/IGD config flow."""
self._discoveries: list[SsdpServiceInfo] | None = None 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.""" """Handle a flow start."""
LOGGER.debug("async_step_user: user_input: %s", user_input) 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, 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: async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
"""Handle a discovered UPnP/IGD device. """Handle a discovered UPnP/IGD device.
@ -275,7 +198,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_step_ssdp_confirm() return await self.async_step_ssdp_confirm()
async def 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: ) -> FlowResult:
"""Confirm integration via SSDP.""" """Confirm integration via SSDP."""
LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input)

View File

@ -156,10 +156,7 @@ async def ssdp_no_discovery():
) as mock_register, patch( ) as mock_register, patch(
"homeassistant.components.ssdp.async_get_discovery_info_by_st", "homeassistant.components.ssdp.async_get_discovery_info_by_st",
return_value=[], return_value=[],
) as mock_get_info, patch( ) as mock_get_info:
"homeassistant.components.upnp.config_flow.SSDP_SEARCH_TIMEOUT",
0.1,
):
yield (mock_register, mock_get_info) yield (mock_register, mock_get_info)

View File

@ -1,7 +1,7 @@
"""Test UPnP/IGD config flow.""" """Test UPnP/IGD config flow."""
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock, patch from unittest.mock import patch
import pytest 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["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "no_devices_found" 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"