diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index 3238f4eb7a8..e8cdb282118 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Renderer", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.28.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index f66592aa9c8..20c38a5be3f 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dms", - "requirements": ["async-upnp-client==0.28.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 292933a4810..9391fe5e311 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -7,7 +7,7 @@ "samsungctl[websocket]==0.7.1", "samsungtvws[async,encrypted]==2.5.0", "wakeonlan==2.0.1", - "async-upnp-client==0.28.0" + "async-upnp-client==0.29.0" ], "ssdp": [ { diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index cdc5fe3242e..b59dc0ce1ee 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -11,12 +11,7 @@ import logging from typing import Any from async_upnp_client.aiohttp import AiohttpSessionRequester -from async_upnp_client.const import ( - AddressTupleVXType, - DeviceOrServiceType, - SsdpHeaders, - SsdpSource, -) +from async_upnp_client.const import AddressTupleVXType, DeviceOrServiceType, SsdpSource from async_upnp_client.description_cache import DescriptionCache from async_upnp_client.ssdp import SSDP_PORT, determine_source_target, is_ipv4_address from async_upnp_client.ssdp_listener import SsdpDevice, SsdpDeviceTracker, SsdpListener @@ -246,13 +241,13 @@ async def _async_process_callbacks( @core_callback def _async_headers_match( - headers: Mapping[str, Any], match_dict: dict[str, str] + headers: CaseInsensitiveDict, lower_match_dict: dict[str, str] ) -> bool: - for header, val in match_dict.items(): + for header, val in lower_match_dict.items(): if val == MATCH_ALL: if header not in headers: return False - elif headers.get(header) != val: + elif headers.get_lower(header) != val: return False return True @@ -328,7 +323,7 @@ class Scanner: @property def _all_headers_from_ssdp_devices( self, - ) -> dict[tuple[str, str], Mapping[str, Any]]: + ) -> dict[tuple[str, str], CaseInsensitiveDict]: return { (ssdp_device.udn, dst): headers for ssdp_device in self._ssdp_devices @@ -340,19 +335,21 @@ class Scanner: ) -> Callable[[], None]: """Register a callback.""" if match_dict is None: - match_dict = {} + lower_match_dict = {} + else: + lower_match_dict = {k.lower(): v for k, v in match_dict.items()} # Make sure any entries that happened # before the callback was registered are fired for headers in self._all_headers_from_ssdp_devices.values(): - if _async_headers_match(headers, match_dict): + if _async_headers_match(headers, lower_match_dict): await _async_process_callbacks( [callback], await self._async_headers_to_discovery_info(headers), SsdpChange.ALIVE, ) - callback_entry = (callback, match_dict) + callback_entry = (callback, lower_match_dict) self._callbacks.append(callback_entry) @core_callback @@ -461,13 +458,13 @@ class Scanner: @core_callback def _async_get_matching_callbacks( self, - combined_headers: SsdpHeaders, + combined_headers: CaseInsensitiveDict, ) -> list[SsdpCallback]: """Return a list of callbacks that match.""" return [ callback - for callback, match_dict in self._callbacks - if _async_headers_match(combined_headers, match_dict) + for callback, lower_match_dict in self._callbacks + if _async_headers_match(combined_headers, lower_match_dict) ] async def _ssdp_listener_callback( @@ -490,9 +487,8 @@ class Scanner: # If there are no changes from a search, do not trigger a config flow if source != SsdpSource.SEARCH_ALIVE: info_desc = await self._async_get_description_dict(location) or {} - assert isinstance(combined_headers, CaseInsensitiveDict) matching_domains = self.integration_matchers.async_matching_domains( - CaseInsensitiveDict({**combined_headers.as_dict(), **info_desc}) + CaseInsensitiveDict(combined_headers.as_dict(), **info_desc) ) if not callbacks and not matching_domains: @@ -530,7 +526,7 @@ class Scanner: return await self._description_cache.async_get_description_dict(location) or {} async def _async_headers_to_discovery_info( - self, headers: Mapping[str, Any] + self, headers: CaseInsensitiveDict ) -> SsdpServiceInfo: """Combine the headers and description into discovery_info. @@ -571,7 +567,7 @@ class Scanner: def discovery_info_from_headers_and_description( - combined_headers: Mapping[str, Any], + combined_headers: CaseInsensitiveDict, info_desc: Mapping[str, Any], ) -> SsdpServiceInfo: """Convert headers and description to discovery_info.""" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index d6702684d04..993a0033b1b 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["async-upnp-client==0.28.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 14756d98023..94d9c8ab058 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.28.0", "getmac==0.8.2"], + "requirements": ["async-upnp-client==0.29.0", "getmac==0.8.2"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman", "@ehendrix23"], "ssdp": [ diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 27e86a37bf6..3df38a41995 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.10", "async-upnp-client==0.28.0"], + "requirements": ["yeelight==0.7.10", "async-upnp-client==0.29.0"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/components/yeelight/scanner.py b/homeassistant/components/yeelight/scanner.py index 01ed163a050..088071244b3 100644 --- a/homeassistant/components/yeelight/scanner.py +++ b/homeassistant/components/yeelight/scanner.py @@ -9,7 +9,8 @@ from ipaddress import IPv4Address import logging from urllib.parse import urlparse -from async_upnp_client.search import SsdpHeaders, SsdpSearchListener +from async_upnp_client.search import SsdpSearchListener +from async_upnp_client.utils import CaseInsensitiveDict from homeassistant import config_entries from homeassistant.components import network, ssdp @@ -46,8 +47,8 @@ class YeelightScanner: """Initialize class.""" self._hass = hass self._host_discovered_events: dict[str, list[asyncio.Event]] = {} - self._unique_id_capabilities: dict[str, SsdpHeaders] = {} - self._host_capabilities: dict[str, SsdpHeaders] = {} + self._unique_id_capabilities: dict[str, CaseInsensitiveDict] = {} + self._host_capabilities: dict[str, CaseInsensitiveDict] = {} self._track_interval: CALLBACK_TYPE | None = None self._listeners: list[SsdpSearchListener] = [] self._connected_events: list[asyncio.Event] = [] @@ -123,7 +124,7 @@ class YeelightScanner: if isinstance(source_ip, IPv4Address) and not source_ip.is_loopback } - async def async_discover(self) -> ValuesView[SsdpHeaders]: + async def async_discover(self) -> ValuesView[CaseInsensitiveDict]: """Discover bulbs.""" _LOGGER.debug("Yeelight discover with interval %s", DISCOVERY_SEARCH_INTERVAL) await self.async_setup() @@ -139,7 +140,7 @@ class YeelightScanner: for listener in self._listeners: listener.async_search() - async def async_get_capabilities(self, host: str) -> SsdpHeaders | None: + async def async_get_capabilities(self, host: str) -> CaseInsensitiveDict | None: """Get capabilities via SSDP.""" if host in self._host_capabilities: return self._host_capabilities[host] @@ -157,7 +158,7 @@ class YeelightScanner: self._host_discovered_events[host].remove(host_event) return self._host_capabilities.get(host) - def _async_discovered_by_ssdp(self, response: SsdpHeaders) -> None: + def _async_discovered_by_ssdp(self, response: CaseInsensitiveDict) -> None: @callback def _async_start_flow(*_) -> None: asyncio.create_task( @@ -177,7 +178,7 @@ class YeelightScanner: # of another discovery async_call_later(self._hass, 1, _async_start_flow) - async def _async_process_entry(self, headers: SsdpHeaders) -> None: + async def _async_process_entry(self, headers: CaseInsensitiveDict) -> None: """Process a discovery.""" _LOGGER.debug("Discovered via SSDP: %s", headers) unique_id = headers["id"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1b9521bd7aa..08e3a019f7b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodiscover==1.4.11 aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.28.0 +async-upnp-client==0.29.0 async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 59e1eb94f0f..e14f4381457 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -333,7 +333,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.28.0 +async-upnp-client==0.29.0 # homeassistant.components.supla asyncpysupla==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 831efb3022f..6a79ca0a773 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -269,7 +269,7 @@ arcam-fmj==0.12.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.28.0 +async-upnp-client==0.29.0 # homeassistant.components.sleepiq asyncsleepiq==1.2.3 diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index 96f8846b174..ea3c8f4656c 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -4,10 +4,11 @@ from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch from async_upnp_client.search import SsdpSearchListener +from async_upnp_client.utils import CaseInsensitiveDict from yeelight import BulbException, BulbType from yeelight.main import _MODEL_SPECS -from homeassistant.components import ssdp, zeroconf +from homeassistant.components import zeroconf from homeassistant.components.yeelight import ( CONF_MODE_MUSIC, CONF_NIGHTLIGHT_SWITCH_TYPE, @@ -157,7 +158,7 @@ def _mocked_bulb(cannot_connect=False): return bulb -def _patched_ssdp_listener(info: ssdp.SsdpHeaders, *args, **kwargs): +def _patched_ssdp_listener(info: CaseInsensitiveDict, *args, **kwargs): listener = SsdpSearchListener(*args, **kwargs) async def _async_callback(*_):