Use async zeroconf registration functions (#50168)

This commit is contained in:
J. Nick Koston 2021-05-11 11:03:36 -05:00 committed by GitHub
parent e616583bad
commit 909a20b36d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 18 deletions

View file

@ -4,7 +4,6 @@ from __future__ import annotations
from collections.abc import Iterable from collections.abc import Iterable
from contextlib import suppress from contextlib import suppress
import fnmatch import fnmatch
from functools import partial
import ipaddress import ipaddress
from ipaddress import ip_address from ipaddress import ip_address
import logging import logging
@ -33,11 +32,10 @@ from homeassistant.const import (
from homeassistant.core import Event, HomeAssistant from homeassistant.core import Event, HomeAssistant
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.singleton import singleton from homeassistant.loader import async_get_homekit, async_get_zeroconf, bind_hass
from homeassistant.loader import async_get_homekit, async_get_zeroconf
from homeassistant.util.network import is_loopback from homeassistant.util.network import is_loopback
from .models import HaServiceBrowser, HaZeroconf from .models import HaAsyncZeroconf, HaServiceBrowser, HaZeroconf
from .usage import install_multiple_zeroconf_catcher from .usage import install_multiple_zeroconf_catcher
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -92,16 +90,26 @@ class HaServiceInfo(TypedDict):
properties: dict[str, Any] properties: dict[str, Any]
@singleton(DOMAIN) @bind_hass
async def async_get_instance(hass: HomeAssistant) -> HaZeroconf: async def async_get_instance(hass: HomeAssistant) -> HaZeroconf:
"""Zeroconf instance to be shared with other integrations that use it."""
return cast(HaZeroconf, (await _async_get_instance(hass)).zeroconf)
@bind_hass
async def async_get_async_instance(hass: HomeAssistant) -> HaAsyncZeroconf:
"""Zeroconf instance to be shared with other integrations that use it.""" """Zeroconf instance to be shared with other integrations that use it."""
return await _async_get_instance(hass) return await _async_get_instance(hass)
async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaZeroconf: async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZeroconf:
if DOMAIN in hass.data:
return cast(HaAsyncZeroconf, hass.data[DOMAIN])
logging.getLogger("zeroconf").setLevel(logging.NOTSET) logging.getLogger("zeroconf").setLevel(logging.NOTSET)
zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs)) aio_zc = HaAsyncZeroconf(**zcargs)
zeroconf = cast(HaZeroconf, aio_zc.zeroconf)
install_multiple_zeroconf_catcher(zeroconf) install_multiple_zeroconf_catcher(zeroconf)
@ -110,8 +118,9 @@ async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaZeroconf:
zeroconf.ha_close() zeroconf.ha_close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_zeroconf) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_zeroconf)
hass.data[DOMAIN] = aio_zc
return zeroconf return aio_zc
def _get_ip_route(dst_ip: str) -> Any: def _get_ip_route(dst_ip: str) -> Any:
@ -171,7 +180,8 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
if not zc_config.get(CONF_IPV6, DEFAULT_IPV6): if not zc_config.get(CONF_IPV6, DEFAULT_IPV6):
zc_args["ip_version"] = IPVersion.V4Only zc_args["ip_version"] = IPVersion.V4Only
zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args) aio_zc = await _async_get_instance(hass, **zc_args)
zeroconf = aio_zc.zeroconf
async def _async_zeroconf_hass_start(_event: Event) -> None: async def _async_zeroconf_hass_start(_event: Event) -> None:
"""Expose Home Assistant on zeroconf when it starts. """Expose Home Assistant on zeroconf when it starts.
@ -179,9 +189,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
Wait till started or otherwise HTTP is not up and running. Wait till started or otherwise HTTP is not up and running.
""" """
uuid = await hass.helpers.instance_id.async_get() uuid = await hass.helpers.instance_id.async_get()
await hass.async_add_executor_job( await _async_register_hass_zc_service(hass, aio_zc, uuid)
_register_hass_zc_service, hass, zeroconf, uuid
)
async def _async_zeroconf_hass_started(_event: Event) -> None: async def _async_zeroconf_hass_started(_event: Event) -> None:
"""Start the service browser.""" """Start the service browser."""
@ -196,8 +204,8 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
return True return True
def _register_hass_zc_service( async def _async_register_hass_zc_service(
hass: HomeAssistant, zeroconf: HaZeroconf, uuid: str hass: HomeAssistant, aio_zc: HaAsyncZeroconf, uuid: str
) -> None: ) -> None:
# Get instance UUID # Get instance UUID
valid_location_name = _truncate_location_name_to_valid(hass.config.location_name) valid_location_name = _truncate_location_name_to_valid(hass.config.location_name)
@ -244,7 +252,7 @@ def _register_hass_zc_service(
_LOGGER.info("Starting Zeroconf broadcast") _LOGGER.info("Starting Zeroconf broadcast")
try: try:
zeroconf.register_service(info) await aio_zc.async_register_service(info)
except NonUniqueNameException: except NonUniqueNameException:
_LOGGER.error( _LOGGER.error(
"Home Assistant instance with identical name present in the local network" "Home Assistant instance with identical name present in the local network"
@ -252,7 +260,7 @@ def _register_hass_zc_service(
async def _async_start_zeroconf_browser( async def _async_start_zeroconf_browser(
hass: HomeAssistant, zeroconf: HaZeroconf hass: HomeAssistant, zeroconf: Zeroconf
) -> None: ) -> None:
"""Start the zeroconf browser.""" """Start the zeroconf browser."""

View file

@ -1,6 +1,10 @@
"""Models for Zeroconf.""" """Models for Zeroconf."""
import asyncio
from typing import Any
from zeroconf import DNSPointer, DNSRecord, ServiceBrowser, Zeroconf from zeroconf import DNSPointer, DNSRecord, ServiceBrowser, Zeroconf
from zeroconf.asyncio import AsyncZeroconf
class HaZeroconf(Zeroconf): class HaZeroconf(Zeroconf):
@ -12,6 +16,20 @@ class HaZeroconf(Zeroconf):
ha_close = Zeroconf.close ha_close = Zeroconf.close
class HaAsyncZeroconf(AsyncZeroconf):
"""Home Assistant version of AsyncZeroconf."""
def __init__( # pylint: disable=super-init-not-called
self, *args: Any, **kwargs: Any
) -> None:
"""Wrap AsyncZeroconf."""
self.zeroconf = HaZeroconf(*args, **kwargs)
self.loop = asyncio.get_running_loop()
async def async_close(self) -> None:
"""Fake method to avoid integrations closing it."""
class HaServiceBrowser(ServiceBrowser): class HaServiceBrowser(ServiceBrowser):
"""ServiceBrowser that only consumes DNSPointer records.""" """ServiceBrowser that only consumes DNSPointer records."""

View file

@ -14,7 +14,7 @@ from tests.components.light.conftest import mock_light_profiles # noqa: F401
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_zeroconf(): def mock_zeroconf():
"""Mock zeroconf.""" """Mock zeroconf."""
with mock.patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: with mock.patch("homeassistant.components.zeroconf.models.HaZeroconf") as mock_zc:
yield mock_zc.return_value yield mock_zc.return_value

View file

@ -478,7 +478,7 @@ async def mqtt_mock(hass, mqtt_client_mock, mqtt_config):
@pytest.fixture @pytest.fixture
def mock_zeroconf(): def mock_zeroconf():
"""Mock zeroconf.""" """Mock zeroconf."""
with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: with patch("homeassistant.components.zeroconf.models.HaZeroconf") as mock_zc:
yield mock_zc.return_value yield mock_zc.return_value