Migrate to using aiohttp-asyncmdnsresolver for aiohttp resolver (#134830)

pull/112047/merge
J. Nick Koston 2025-01-06 12:06:28 -10:00 committed by GitHub
parent d13c14eedb
commit 89c73f56b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 9 deletions

View File

@ -144,17 +144,27 @@ class ZeroconfServiceInfo(BaseServiceInfo):
@bind_hass
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)
"""Get or create the shared HaZeroconf instance."""
return cast(HaZeroconf, (_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."""
return await _async_get_instance(hass)
"""Get or create the shared HaAsyncZeroconf instance."""
return _async_get_instance(hass)
async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZeroconf:
@callback
def async_get_async_zeroconf(hass: HomeAssistant) -> HaAsyncZeroconf:
"""Get or create the shared HaAsyncZeroconf instance.
This method must be run in the event loop, and is an alternative
to the async_get_async_instance method when a coroutine cannot be used.
"""
return _async_get_instance(hass)
def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZeroconf:
if DOMAIN in hass.data:
return cast(HaAsyncZeroconf, hass.data[DOMAIN])
@ -221,7 +231,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
]
aio_zc = await _async_get_instance(hass, **zc_args)
aio_zc = _async_get_instance(hass, **zc_args)
zeroconf = cast(HaZeroconf, aio_zc.zeroconf)
zeroconf_types = await async_get_zeroconf(hass)
homekit_models = await async_get_homekit(hass)

View File

@ -14,10 +14,11 @@ from typing import TYPE_CHECKING, Any, Self
import aiohttp
from aiohttp import web
from aiohttp.hdrs import CONTENT_TYPE, USER_AGENT
from aiohttp.resolver import AsyncResolver
from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout
from aiohttp_asyncmdnsresolver.api import AsyncMDNSResolver
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import APPLICATION_NAME, EVENT_HOMEASSISTANT_CLOSE, __version__
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.loader import bind_hass
@ -362,7 +363,7 @@ def _async_get_connector(
ssl=ssl_context,
limit=MAXIMUM_CONNECTIONS,
limit_per_host=MAXIMUM_CONNECTIONS_PER_HOST,
resolver=AsyncResolver(),
resolver=_async_make_resolver(hass),
)
connectors[connector_key] = connector
@ -373,3 +374,8 @@ def _async_get_connector(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_connector)
return connector
@callback
def _async_make_resolver(hass: HomeAssistant) -> AsyncMDNSResolver:
return AsyncMDNSResolver(async_zeroconf=zeroconf.async_get_async_zeroconf(hass))

View File

@ -4,6 +4,7 @@ aiodhcpwatcher==1.0.2
aiodiscover==2.1.0
aiodns==3.2.0
aiohasupervisor==0.2.2b5
aiohttp-asyncmdnsresolver==0.0.1
aiohttp-fast-zlib==0.2.0
aiohttp==3.11.11
aiohttp_cors==0.7.0

View File

@ -32,6 +32,7 @@ dependencies = [
"aiohttp==3.11.11",
"aiohttp_cors==0.7.0",
"aiohttp-fast-zlib==0.2.0",
"aiohttp-asyncmdnsresolver==0.0.1",
"aiozoneinfo==0.2.1",
"astral==2.2",
"async-interrupt==1.2.0",
@ -82,6 +83,7 @@ dependencies = [
"voluptuous-openapi==0.0.5",
"yarl==1.18.3",
"webrtc-models==0.3.0",
"zeroconf==0.136.2"
]
[project.urls]

View File

@ -8,6 +8,7 @@ aiohasupervisor==0.2.2b5
aiohttp==3.11.11
aiohttp_cors==0.7.0
aiohttp-fast-zlib==0.2.0
aiohttp-asyncmdnsresolver==0.0.1
aiozoneinfo==0.2.1
astral==2.2
async-interrupt==1.2.0
@ -50,3 +51,4 @@ voluptuous-serialize==2.6.0
voluptuous-openapi==0.0.5
yarl==1.18.3
webrtc-models==0.3.0
zeroconf==0.136.2

View File

@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any, cast
from unittest.mock import AsyncMock, MagicMock, Mock, _patch, patch
from aiohttp import client
from aiohttp.resolver import AsyncResolver
from aiohttp.test_utils import (
BaseTestServer,
TestClient,
@ -1220,6 +1221,30 @@ def disable_translations_once(
translations_once.start()
@pytest.fixture(autouse=True, scope="session")
def mock_zeroconf_resolver() -> Generator[_patch]:
"""Mock out the zeroconf resolver."""
patcher = patch(
"homeassistant.helpers.aiohttp_client._async_make_resolver",
return_value=AsyncResolver(),
)
patcher.start()
try:
yield patcher
finally:
patcher.stop()
@pytest.fixture
def disable_mock_zeroconf_resolver(
mock_zeroconf_resolver: _patch,
) -> Generator[None]:
"""Disable the zeroconf resolver."""
mock_zeroconf_resolver.stop()
yield
mock_zeroconf_resolver.start()
@pytest.fixture
def mock_zeroconf() -> Generator[MagicMock]:
"""Mock zeroconf."""

View File

@ -390,3 +390,16 @@ async def test_client_session_immutable_headers(hass: HomeAssistant) -> None:
with pytest.raises(AttributeError):
session.headers.update({"user-agent": "bla"})
@pytest.mark.usefixtures("disable_mock_zeroconf_resolver")
@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_async_mdnsresolver(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test async_mdnsresolver."""
resp = aioclient_mock.post("http://localhost/xyz", json={"x": 1})
session = client.async_create_clientsession(hass)
resp = await session.post("http://localhost/xyz", json={"x": 1})
assert resp.status == 200
assert await resp.json() == {"x": 1}

View File

@ -1392,9 +1392,13 @@ async def test_bootstrap_does_not_preload_stage_1_integrations() -> None:
assert process.returncode == 0
decoded_stdout = stdout.decode()
disallowed_integrations = bootstrap.STAGE_1_INTEGRATIONS.copy()
# zeroconf is a top level dep now
disallowed_integrations.remove("zeroconf")
# Ensure no stage1 integrations have been imported
# as a side effect of importing the pre-imports
for integration in bootstrap.STAGE_1_INTEGRATIONS:
for integration in disallowed_integrations:
assert f"homeassistant.components.{integration}" not in decoded_stdout