diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index c7568d7ae25..7394936f355 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -17,7 +17,7 @@ from homeassistant.components.alexa import ( ) from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_BAD_REQUEST from homeassistant.core import HomeAssistant, callback, split_entity_id -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry, start from homeassistant.helpers.event import async_call_later from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -107,8 +107,12 @@ class AlexaConfig(alexa_config.AbstractConfig): async def async_initialize(self): """Initialize the Alexa config.""" - if self.enabled and ALEXA_DOMAIN not in self.hass.config.components: - await async_setup_component(self.hass, ALEXA_DOMAIN, {}) + + async def hass_started(hass): + if self.enabled and ALEXA_DOMAIN not in self.hass.config.components: + await async_setup_component(self.hass, ALEXA_DOMAIN, {}) + + start.async_at_start(self.hass, hass_started) def should_expose(self, entity_id): """If an entity should be exposed.""" diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 41f62c32c39..65cbe8bb342 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -9,7 +9,7 @@ from homeassistant.components.google_assistant.const import DOMAIN as GOOGLE_DOM from homeassistant.components.google_assistant.helpers import AbstractConfig from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_OK from homeassistant.core import CoreState, split_entity_id -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry, start from homeassistant.setup import async_setup_component from .const import ( @@ -86,8 +86,11 @@ class CloudGoogleConfig(AbstractConfig): """Perform async initialization of config.""" await super().async_initialize() - if self.enabled and GOOGLE_DOMAIN not in self.hass.config.components: - await async_setup_component(self.hass, GOOGLE_DOMAIN, {}) + async def hass_started(hass): + if self.enabled and GOOGLE_DOMAIN not in self.hass.config.components: + await async_setup_component(self.hass, GOOGLE_DOMAIN, {}) + + start.async_at_start(self.hass, hass_started) # Remove old/wrong user agent ids remove_agent_user_ids = [] diff --git a/homeassistant/core.py b/homeassistant/core.py index 7b5c93b15bb..b9bf97e7e6c 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -374,7 +374,7 @@ class HomeAssistant: return task - def create_task(self, target: Coroutine) -> None: + def create_task(self, target: Awaitable) -> None: """Add task to the executor pool. target: target to call. @@ -382,7 +382,7 @@ class HomeAssistant: self.loop.call_soon_threadsafe(self.async_create_task, target) @callback - def async_create_task(self, target: Coroutine) -> asyncio.tasks.Task: + def async_create_task(self, target: Awaitable) -> asyncio.tasks.Task: """Create a task from within the eventloop. This method must be run in the event loop. diff --git a/homeassistant/helpers/start.py b/homeassistant/helpers/start.py new file mode 100644 index 00000000000..e7e827ec5c3 --- /dev/null +++ b/homeassistant/helpers/start.py @@ -0,0 +1,25 @@ +"""Helpers to help during startup.""" +from collections.abc import Awaitable +from typing import Callable + +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.core import Event, HomeAssistant, callback + + +@callback +def async_at_start( + hass: HomeAssistant, at_start_cb: Callable[[HomeAssistant], Awaitable] +) -> None: + """Execute something when Home Assistant is started. + + Will execute it now if Home Assistant is already started. + """ + if hass.is_running: + hass.async_create_task(at_start_cb(hass)) + return + + async def _matched_event(event: Event) -> None: + """Call the callback when Home Assistant started.""" + await at_start_cb(hass) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _matched_event) diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index bc430347e08..64d50250259 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -234,6 +234,7 @@ async def test_setup_integration(hass, mock_conf, cloud_prefs): assert "google_assistant" not in hass.config.components await mock_conf.async_initialize() + await hass.async_block_till_done() assert "google_assistant" in hass.config.components hass.config.components.remove("google_assistant") diff --git a/tests/helpers/test_start.py b/tests/helpers/test_start.py new file mode 100644 index 00000000000..35838f1ceaa --- /dev/null +++ b/tests/helpers/test_start.py @@ -0,0 +1,39 @@ +"""Test starting HA helpers.""" +from homeassistant import core +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.helpers import start + + +async def test_at_start_when_running(hass): + """Test at start when already running.""" + assert hass.is_running + + calls = [] + + async def cb_at_start(hass): + """Home Assistant is started.""" + calls.append(1) + + start.async_at_start(hass, cb_at_start) + await hass.async_block_till_done() + assert len(calls) == 1 + + +async def test_at_start_when_starting(hass): + """Test at start when yet to start.""" + hass.state = core.CoreState.not_running + assert not hass.is_running + + calls = [] + + async def cb_at_start(hass): + """Home Assistant is started.""" + calls.append(1) + + start.async_at_start(hass, cb_at_start) + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + assert len(calls) == 1