diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index a3490ad8037..c7988fe98c9 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -121,6 +121,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: cancel_discovered_callback() # Create device. + assert discovery_info is not None + assert discovery_info.ssdp_location is not None location = discovery_info.ssdp_location try: device = await Device.async_create_device(hass, location) diff --git a/homeassistant/components/upnp/binary_sensor.py b/homeassistant/components/upnp/binary_sensor.py index d848aff5a88..d49bbddf996 100644 --- a/homeassistant/components/upnp/binary_sensor.py +++ b/homeassistant/components/upnp/binary_sensor.py @@ -43,6 +43,7 @@ async def async_setup_entry( class UpnpStatusBinarySensor(UpnpEntity, BinarySensorEntity): """Class for UPnP/IGD binary sensors.""" + entity_description: UpnpBinarySensorEntityDescription _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY def __init__( diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 1b8507d25bf..e339c69d5d8 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import Mapping from datetime import timedelta -from typing import Any +from typing import Any, cast import voluptuous as vol @@ -31,16 +31,17 @@ from .const import ( def _friendly_name_from_discovery(discovery_info: ssdp.SsdpServiceInfo) -> str: """Extract user-friendly name from discovery.""" - return ( + return cast( + str, discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) or discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME) - or discovery_info.ssdp_headers.get("_host", "") + or discovery_info.ssdp_headers.get("_host", ""), ) def _is_complete_discovery(discovery_info: ssdp.SsdpServiceInfo) -> bool: """Test if discovery is complete and usable.""" - return ( + return bool( ssdp.ATTR_UPNP_UDN in discovery_info.upnp and discovery_info.ssdp_st and discovery_info.ssdp_location @@ -114,14 +115,13 @@ 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 - ) -> Mapping[str, Any]: + async def async_step_user(self, user_input: Mapping | None = None) -> FlowResult: """Handle a flow start.""" LOGGER.debug("async_step_user: user_input: %s", user_input) if user_input is not None: # Ensure wanted device was discovered. + assert self._discoveries matching_discoveries = [ discovery for discovery in self._discoveries @@ -248,12 +248,13 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp_confirm( self, user_input: Mapping | None = None - ) -> Mapping[str, Any]: + ) -> FlowResult: """Confirm integration via SSDP.""" LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) if user_input is None: return self.async_show_form(step_id="ssdp_confirm") + assert self._discoveries discovery = self._discoveries[0] return await self._async_create_entry_from_discovery(discovery) @@ -268,7 +269,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_create_entry_from_discovery( self, discovery: SsdpServiceInfo, - ) -> Mapping[str, Any]: + ) -> FlowResult: """Create an entry from discovery.""" LOGGER.debug( "_async_create_entry_from_discovery: discovery: %s", @@ -291,7 +292,7 @@ class UpnpOptionsFlowHandler(config_entries.OptionsFlow): """Initialize.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Mapping = None) -> None: + async def async_step_init(self, user_input: Mapping = None) -> FlowResult: """Manage the options.""" if user_input is not None: coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id] diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index be0ae725396..19e7987a138 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -9,7 +9,7 @@ from urllib.parse import urlparse from async_upnp_client import UpnpDevice, UpnpFactory from async_upnp_client.aiohttp import AiohttpSessionRequester from async_upnp_client.exceptions import UpnpError -from async_upnp_client.profiles.igd import IgdDevice +from async_upnp_client.profiles.igd import IgdDevice, StatusInfo from homeassistant.components import ssdp from homeassistant.components.ssdp import SsdpChange, SsdpServiceInfo @@ -49,7 +49,7 @@ class Device: """Initialize UPnP/IGD device.""" self.hass = hass self._igd_device = igd_device - self.coordinator: DataUpdateCoordinator = None + self.coordinator: DataUpdateCoordinator | None = None @classmethod async def async_create_device( @@ -129,7 +129,7 @@ class Device: return self.usn @property - def hostname(self) -> str: + def hostname(self) -> str | None: """Get the hostname.""" url = self._igd_device.device.device_url parsed = urlparse(url) @@ -177,7 +177,9 @@ class Device: self._igd_device.async_get_external_ip_address(), return_exceptions=True, ) - result = [] + status_info: StatusInfo | None = None + ip_address: str | None = None + for idx, value in enumerate(values): if isinstance(value, UpnpError): # Not all routers support some of these items although based @@ -188,16 +190,18 @@ class Device: self, str(value), ) - result.append(None) continue if isinstance(value, Exception): raise value - result.append(value) + if isinstance(value, StatusInfo): + status_info = value + elif isinstance(value, str): + ip_address = value return { - WAN_STATUS: result[0][0] if result[0] is not None else None, - ROUTER_UPTIME: result[0][2] if result[0] is not None else None, - ROUTER_IP: result[1], + WAN_STATUS: status_info[0] if status_info is not None else None, + ROUTER_UPTIME: status_info[2] if status_info is not None else None, + ROUTER_IP: ip_address, } diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 595a14a5a9f..908a5b53940 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -143,6 +143,8 @@ async def async_setup_entry( class UpnpSensor(UpnpEntity, SensorEntity): """Base class for UPnP/IGD sensors.""" + entity_description: UpnpSensorEntityDescription + class RawUpnpSensor(UpnpSensor): """Representation of a UPnP/IGD sensor.""" @@ -159,8 +161,6 @@ class RawUpnpSensor(UpnpSensor): class DerivedUpnpSensor(UpnpSensor): """Representation of a UNIT Sent/Received per second sensor.""" - entity_description: UpnpSensorEntityDescription - def __init__( self, coordinator: UpnpDataUpdateCoordinator, diff --git a/mypy.ini b/mypy.ini index d674160c8be..6316ce32abf 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2725,21 +2725,6 @@ ignore_errors = true [mypy-homeassistant.components.unifi.unifi_entity_base] ignore_errors = true -[mypy-homeassistant.components.upnp] -ignore_errors = true - -[mypy-homeassistant.components.upnp.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.upnp.config_flow] -ignore_errors = true - -[mypy-homeassistant.components.upnp.device] -ignore_errors = true - -[mypy-homeassistant.components.upnp.sensor] -ignore_errors = true - [mypy-homeassistant.components.vizio.config_flow] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index dc8dc2188d3..5df83f1e527 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -150,11 +150,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.unifi.device_tracker", "homeassistant.components.unifi.diagnostics", "homeassistant.components.unifi.unifi_entity_base", - "homeassistant.components.upnp", - "homeassistant.components.upnp.binary_sensor", - "homeassistant.components.upnp.config_flow", - "homeassistant.components.upnp.device", - "homeassistant.components.upnp.sensor", "homeassistant.components.vizio.config_flow", "homeassistant.components.vizio.media_player", "homeassistant.components.withings",