mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 13:57:10 +00:00
Reduce zeroconf matcher complexity (#105880)
This commit is contained in:
parent
321dc3984c
commit
bb30bfa225
@ -33,6 +33,7 @@ from homeassistant.helpers.network import NoURLAvailableError, get_url
|
|||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import (
|
from homeassistant.loader import (
|
||||||
HomeKitDiscoveredIntegration,
|
HomeKitDiscoveredIntegration,
|
||||||
|
ZeroconfMatcher,
|
||||||
async_get_homekit,
|
async_get_homekit,
|
||||||
async_get_zeroconf,
|
async_get_zeroconf,
|
||||||
bind_hass,
|
bind_hass,
|
||||||
@ -54,9 +55,6 @@ HOMEKIT_TYPES = [
|
|||||||
]
|
]
|
||||||
_HOMEKIT_MODEL_SPLITS = (None, " ", "-")
|
_HOMEKIT_MODEL_SPLITS = (None, " ", "-")
|
||||||
|
|
||||||
# Top level keys we support matching against in properties that are always matched in
|
|
||||||
# lower case. ex: ZeroconfServiceInfo.name
|
|
||||||
LOWER_MATCH_ATTRS = {"name"}
|
|
||||||
|
|
||||||
CONF_DEFAULT_INTERFACE = "default_interface"
|
CONF_DEFAULT_INTERFACE = "default_interface"
|
||||||
CONF_IPV6 = "ipv6"
|
CONF_IPV6 = "ipv6"
|
||||||
@ -74,6 +72,8 @@ MAX_PROPERTY_VALUE_LEN = 230
|
|||||||
# Dns label max length
|
# Dns label max length
|
||||||
MAX_NAME_LEN = 63
|
MAX_NAME_LEN = 63
|
||||||
|
|
||||||
|
ATTR_DOMAIN: Final = "domain"
|
||||||
|
ATTR_NAME: Final = "name"
|
||||||
ATTR_PROPERTIES: Final = "properties"
|
ATTR_PROPERTIES: Final = "properties"
|
||||||
|
|
||||||
# Attributes for ZeroconfServiceInfo[ATTR_PROPERTIES]
|
# Attributes for ZeroconfServiceInfo[ATTR_PROPERTIES]
|
||||||
@ -319,24 +319,6 @@ async def _async_register_hass_zc_service(
|
|||||||
await aio_zc.async_register_service(info, allow_name_change=True)
|
await aio_zc.async_register_service(info, allow_name_change=True)
|
||||||
|
|
||||||
|
|
||||||
def _match_against_data(
|
|
||||||
matcher: dict[str, str | dict[str, str]], match_data: dict[str, str]
|
|
||||||
) -> bool:
|
|
||||||
"""Check a matcher to ensure all values in match_data match."""
|
|
||||||
for key in LOWER_MATCH_ATTRS:
|
|
||||||
if key not in matcher:
|
|
||||||
continue
|
|
||||||
if key not in match_data:
|
|
||||||
return False
|
|
||||||
match_val = matcher[key]
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert isinstance(match_val, str)
|
|
||||||
|
|
||||||
if not _memorized_fnmatch(match_data[key], match_val):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _match_against_props(matcher: dict[str, str], props: dict[str, str | None]) -> bool:
|
def _match_against_props(matcher: dict[str, str], props: dict[str, str | None]) -> bool:
|
||||||
"""Check a matcher to ensure all values in props."""
|
"""Check a matcher to ensure all values in props."""
|
||||||
return not any(
|
return not any(
|
||||||
@ -365,7 +347,7 @@ class ZeroconfDiscovery:
|
|||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
zeroconf: HaZeroconf,
|
zeroconf: HaZeroconf,
|
||||||
zeroconf_types: dict[str, list[dict[str, str | dict[str, str]]]],
|
zeroconf_types: dict[str, list[ZeroconfMatcher]],
|
||||||
homekit_model_lookups: dict[str, HomeKitDiscoveredIntegration],
|
homekit_model_lookups: dict[str, HomeKitDiscoveredIntegration],
|
||||||
homekit_model_matchers: dict[re.Pattern, HomeKitDiscoveredIntegration],
|
homekit_model_matchers: dict[re.Pattern, HomeKitDiscoveredIntegration],
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -496,27 +478,23 @@ class ZeroconfDiscovery:
|
|||||||
# discover it, we can stop here.
|
# discover it, we can stop here.
|
||||||
return
|
return
|
||||||
|
|
||||||
match_data: dict[str, str] = {}
|
if not (matchers := self.zeroconf_types.get(service_type)):
|
||||||
for key in LOWER_MATCH_ATTRS:
|
return
|
||||||
attr_value: str = getattr(info, key)
|
|
||||||
match_data[key] = attr_value.lower()
|
|
||||||
|
|
||||||
# Not all homekit types are currently used for discovery
|
# Not all homekit types are currently used for discovery
|
||||||
# so not all service type exist in zeroconf_types
|
# so not all service type exist in zeroconf_types
|
||||||
for matcher in self.zeroconf_types.get(service_type, []):
|
for matcher in matchers:
|
||||||
if len(matcher) > 1:
|
if len(matcher) > 1:
|
||||||
if not _match_against_data(matcher, match_data):
|
if ATTR_NAME in matcher and not _memorized_fnmatch(
|
||||||
|
info.name.lower(), matcher[ATTR_NAME]
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if ATTR_PROPERTIES in matcher:
|
if ATTR_PROPERTIES in matcher and not _match_against_props(
|
||||||
matcher_props = matcher[ATTR_PROPERTIES]
|
matcher[ATTR_PROPERTIES], props
|
||||||
if TYPE_CHECKING:
|
):
|
||||||
assert isinstance(matcher_props, dict)
|
|
||||||
if not _match_against_props(matcher_props, props):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
matcher_domain = matcher["domain"]
|
matcher_domain = matcher[ATTR_DOMAIN]
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert isinstance(matcher_domain, str)
|
|
||||||
context = {
|
context = {
|
||||||
"source": config_entries.SOURCE_ZEROCONF,
|
"source": config_entries.SOURCE_ZEROCONF,
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,14 @@ class HomeKitDiscoveredIntegration:
|
|||||||
always_discover: bool
|
always_discover: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroconfMatcher(TypedDict, total=False):
|
||||||
|
"""Matcher for zeroconf."""
|
||||||
|
|
||||||
|
domain: str
|
||||||
|
name: str
|
||||||
|
properties: dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
class Manifest(TypedDict, total=False):
|
class Manifest(TypedDict, total=False):
|
||||||
"""Integration manifest.
|
"""Integration manifest.
|
||||||
|
|
||||||
@ -374,7 +382,7 @@ async def async_get_application_credentials(hass: HomeAssistant) -> list[str]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def async_process_zeroconf_match_dict(entry: dict[str, Any]) -> dict[str, Any]:
|
def async_process_zeroconf_match_dict(entry: dict[str, Any]) -> ZeroconfMatcher:
|
||||||
"""Handle backwards compat with zeroconf matchers."""
|
"""Handle backwards compat with zeroconf matchers."""
|
||||||
entry_without_type: dict[str, Any] = entry.copy()
|
entry_without_type: dict[str, Any] = entry.copy()
|
||||||
del entry_without_type["type"]
|
del entry_without_type["type"]
|
||||||
@ -396,21 +404,21 @@ def async_process_zeroconf_match_dict(entry: dict[str, Any]) -> dict[str, Any]:
|
|||||||
else:
|
else:
|
||||||
prop_dict = entry_without_type["properties"]
|
prop_dict = entry_without_type["properties"]
|
||||||
prop_dict[moved_prop] = value.lower()
|
prop_dict[moved_prop] = value.lower()
|
||||||
return entry_without_type
|
return cast(ZeroconfMatcher, entry_without_type)
|
||||||
|
|
||||||
|
|
||||||
async def async_get_zeroconf(
|
async def async_get_zeroconf(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> dict[str, list[dict[str, str | dict[str, str]]]]:
|
) -> dict[str, list[ZeroconfMatcher]]:
|
||||||
"""Return cached list of zeroconf types."""
|
"""Return cached list of zeroconf types."""
|
||||||
zeroconf: dict[str, list[dict[str, str | dict[str, str]]]] = ZEROCONF.copy() # type: ignore[assignment]
|
zeroconf: dict[str, list[ZeroconfMatcher]] = ZEROCONF.copy() # type: ignore[assignment]
|
||||||
|
|
||||||
integrations = await async_get_custom_components(hass)
|
integrations = await async_get_custom_components(hass)
|
||||||
for integration in integrations.values():
|
for integration in integrations.values():
|
||||||
if not integration.zeroconf:
|
if not integration.zeroconf:
|
||||||
continue
|
continue
|
||||||
for entry in integration.zeroconf:
|
for entry in integration.zeroconf:
|
||||||
data: dict[str, str | dict[str, str]] = {"domain": integration.domain}
|
data: ZeroconfMatcher = {"domain": integration.domain}
|
||||||
if isinstance(entry, dict):
|
if isinstance(entry, dict):
|
||||||
typ = entry["type"]
|
typ = entry["type"]
|
||||||
data.update(async_process_zeroconf_match_dict(entry))
|
data.update(async_process_zeroconf_match_dict(entry))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user