Replace http startup logic with async_when_setup_or_start (#48784)

This commit is contained in:
J. Nick Koston 2021-04-13 11:10:58 -10:00 committed by GitHub
parent d7ac4bd653
commit 81e6ad0744
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 41 deletions

View file

@ -6,22 +6,18 @@ from ipaddress import ip_network
import logging import logging
import os import os
import ssl import ssl
from typing import Optional, cast from typing import Any, Optional, cast
from aiohttp import web from aiohttp import web
from aiohttp.web_exceptions import HTTPMovedPermanently from aiohttp.web_exceptions import HTTPMovedPermanently
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
SERVER_PORT,
)
from homeassistant.core import Event, HomeAssistant from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers import storage from homeassistant.helpers import storage
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED from homeassistant.setup import async_start_setup, async_when_setup_or_start
import homeassistant.util as hass_util import homeassistant.util as hass_util
from homeassistant.util import ssl as ssl_util from homeassistant.util import ssl as ssl_util
@ -161,36 +157,17 @@ async def async_setup(hass, config):
ssl_profile=ssl_profile, ssl_profile=ssl_profile,
) )
startup_listeners = []
async def stop_server(event: Event) -> None: async def stop_server(event: Event) -> None:
"""Stop the server.""" """Stop the server."""
await server.stop() await server.stop()
async def start_server(event: Event) -> None: async def start_server(*_: Any) -> None:
"""Start the server.""" """Start the server."""
with async_start_setup(hass, ["http"]):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server)
await start_http_server_and_save_config(hass, dict(conf), server)
for listener in startup_listeners: async_when_setup_or_start(hass, "frontend", start_server)
listener()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server)
await start_http_server_and_save_config(hass, dict(conf), server)
async def async_wait_frontend_load(event: Event) -> None:
"""Wait for the frontend to load."""
if event.data[ATTR_COMPONENT] != "frontend":
return
await start_server(event)
startup_listeners.append(
hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_wait_frontend_load)
)
startup_listeners.append(
hass.bus.async_listen(EVENT_HOMEASSISTANT_START, start_server)
)
hass.http = server hass.http = server

View file

@ -10,7 +10,11 @@ from typing import Awaitable, Callable, Generator, Iterable
from homeassistant import config as conf_util, core, loader, requirements from homeassistant import config as conf_util, core, loader, requirements
from homeassistant.config import async_notify_setup_error from homeassistant.config import async_notify_setup_error
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.const import (
EVENT_COMPONENT_LOADED,
EVENT_HOMEASSISTANT_START,
PLATFORM_FORMAT,
)
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util, ensure_unique_string from homeassistant.util import dt as dt_util, ensure_unique_string
@ -379,6 +383,27 @@ def async_when_setup(
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]], when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
) -> None: ) -> None:
"""Call a method when a component is setup.""" """Call a method when a component is setup."""
_async_when_setup(hass, component, when_setup_cb, False)
@core.callback
def async_when_setup_or_start(
hass: core.HomeAssistant,
component: str,
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
) -> None:
"""Call a method when a component is setup or state is fired."""
_async_when_setup(hass, component, when_setup_cb, True)
@core.callback
def _async_when_setup(
hass: core.HomeAssistant,
component: str,
when_setup_cb: Callable[[core.HomeAssistant, str], Awaitable[None]],
start_event: bool,
) -> None:
"""Call a method when a component is setup or the start event fires."""
async def when_setup() -> None: async def when_setup() -> None:
"""Call the callback.""" """Call the callback."""
@ -387,22 +412,28 @@ def async_when_setup(
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error handling when_setup callback for %s", component) _LOGGER.exception("Error handling when_setup callback for %s", component)
# Running it in a new task so that it always runs after
if component in hass.config.components: if component in hass.config.components:
hass.async_create_task(when_setup()) hass.async_create_task(when_setup())
return return
unsub = None listeners: list[Callable] = []
async def loaded_event(event: core.Event) -> None: async def _matched_event(event: core.Event) -> None:
"""Call the callback.""" """Call the callback when we matched an event."""
if event.data[ATTR_COMPONENT] != component: for listener in listeners:
return listener()
unsub() # type: ignore
await when_setup() await when_setup()
unsub = hass.bus.async_listen(EVENT_COMPONENT_LOADED, loaded_event) async def _loaded_event(event: core.Event) -> None:
"""Call the callback if we loaded the expected component."""
if event.data[ATTR_COMPONENT] == component:
await _matched_event(event)
listeners.append(hass.bus.async_listen(EVENT_COMPONENT_LOADED, _loaded_event))
if start_event:
listeners.append(
hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _matched_event)
)
@core.callback @core.callback

View file

@ -556,6 +556,41 @@ async def test_when_setup_already_loaded(hass):
assert calls == ["test", "test"] assert calls == ["test", "test"]
async def test_async_when_setup_or_start_already_loaded(hass):
"""Test when setup or start."""
calls = []
async def mock_callback(hass, component):
"""Mock callback."""
calls.append(component)
setup.async_when_setup_or_start(hass, "test", mock_callback)
await hass.async_block_till_done()
assert calls == []
hass.config.components.add("test")
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {"component": "test"})
await hass.async_block_till_done()
assert calls == ["test"]
# Event listener should be gone
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {"component": "test"})
await hass.async_block_till_done()
assert calls == ["test"]
# Should be called right away
setup.async_when_setup_or_start(hass, "test", mock_callback)
await hass.async_block_till_done()
assert calls == ["test", "test"]
setup.async_when_setup_or_start(hass, "not_loaded", mock_callback)
await hass.async_block_till_done()
assert calls == ["test", "test"]
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert calls == ["test", "test", "not_loaded"]
async def test_setup_import_blows_up(hass): async def test_setup_import_blows_up(hass):
"""Test that we handle it correctly when importing integration blows up.""" """Test that we handle it correctly when importing integration blows up."""
with patch( with patch(