From 80207835d7e98fc7e48f8bcc4e906e564c63bd13 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Jan 2024 20:09:48 +0100 Subject: [PATCH] Move core fundamental components into bootstrap (#105560) Co-authored-by: Erik Co-authored-by: Martin Hjelmare --- homeassistant/bootstrap.py | 68 ++++++++++++++++--- .../components/default_config/__init__.py | 5 -- .../components/default_config/manifest.json | 23 +------ tests/components/default_config/test_init.py | 6 ++ tests/test_bootstrap.py | 18 +++-- 5 files changed, 80 insertions(+), 40 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index bca74a684b2..cc3d87319d0 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -39,7 +39,6 @@ from .helpers import ( from .helpers.dispatcher import async_dispatcher_send from .helpers.typing import ConfigType from .setup import ( - DATA_SETUP, DATA_SETUP_STARTED, DATA_SETUP_TIME, async_notify_setup_error, @@ -106,6 +105,52 @@ STAGE_1_INTEGRATIONS = { # Ensure supervisor is available "hassio", } +DEFAULT_INTEGRATIONS = { + # These integrations are set up unless recovery mode is activated. + # + # Integrations providing core functionality: + "application_credentials", + "frontend", + "hardware", + "logger", + "network", + "system_health", + # + # Key-feature: + "automation", + "person", + "scene", + "script", + "tag", + "zone", + # + # Built-in helpers: + "counter", + "input_boolean", + "input_button", + "input_datetime", + "input_number", + "input_select", + "input_text", + "schedule", + "timer", +} +DEFAULT_INTEGRATIONS_RECOVERY_MODE = { + # These integrations are set up if recovery mode is activated. + "frontend", +} +DEFAULT_INTEGRATIONS_SUPERVISOR = { + # These integrations are set up if using the Supervisor + "hassio", +} +DEFAULT_INTEGRATIONS_NON_SUPERVISOR = { + # These integrations are set up if not using the Supervisor + "backup", +} +CRITICAL_INTEGRATIONS = { + # Recovery mode is activated if these integrations fail to set up + "frontend", +} async def async_setup_hass( @@ -165,11 +210,11 @@ async def async_setup_hass( _LOGGER.warning("Unable to set up core integrations. Activating recovery mode") recovery_mode = True - elif ( - "frontend" in hass.data.get(DATA_SETUP, {}) - and "frontend" not in hass.config.components - ): - _LOGGER.warning("Detected that frontend did not load. Activating recovery mode") + elif any(domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS): + _LOGGER.warning( + "Detected that %s did not load. Activating recovery mode", + ",".join(CRITICAL_INTEGRATIONS), + ) # Ask integrations to shut down. It's messy but we can't # do a clean stop without knowing what is broken with contextlib.suppress(asyncio.TimeoutError): @@ -478,13 +523,18 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]: domain for key in config if (domain := cv.domain_key(key)) != core.DOMAIN } - # Add config entry domains + # Add config entry and default domains if not hass.config.recovery_mode: + domains.update(DEFAULT_INTEGRATIONS) domains.update(hass.config_entries.async_domains()) + else: + domains.update(DEFAULT_INTEGRATIONS_RECOVERY_MODE) - # Make sure the Hass.io component is loaded + # Add domains depending on if the Supervisor is used or not if "SUPERVISOR" in os.environ: - domains.add("hassio") + domains.update(DEFAULT_INTEGRATIONS_SUPERVISOR) + else: + domains.update(DEFAULT_INTEGRATIONS_NON_SUPERVISOR) return domains diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py index 25a9ca311e8..2221bbbef61 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -1,9 +1,7 @@ """Component providing default configuration for new users.""" -from homeassistant.components.hassio import is_hassio from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType -from homeassistant.setup import async_setup_component DOMAIN = "default_config" @@ -12,7 +10,4 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Initialize default configuration.""" - if not is_hassio(hass): - await async_setup_component(hass, "backup", config) - return True diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index 684013a5633..cbadb704a42 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -3,46 +3,25 @@ "name": "Default Config", "codeowners": ["@home-assistant/core"], "dependencies": [ - "application_credentials", "assist_pipeline", - "automation", "bluetooth", "cloud", "conversation", - "counter", "dhcp", "energy", - "frontend", - "hardware", "history", "homeassistant_alerts", - "input_boolean", - "input_button", - "input_datetime", - "input_number", - "input_select", - "input_text", "logbook", - "logger", "map", "media_source", "mobile_app", "my", - "network", - "person", - "scene", - "schedule", - "script", "ssdp", "stream", "sun", - "system_health", - "tag", - "timer", "usb", "webhook", - "zeroconf", - "zone" + "zeroconf" ], "documentation": "https://www.home-assistant.io/integrations/default_config", "integration_type": "system", diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index f3907aac548..20029fe3cdc 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest +from homeassistant import bootstrap from homeassistant.core import HomeAssistant from homeassistant.helpers import recorder as recorder_helper from homeassistant.setup import async_setup_component @@ -34,4 +35,9 @@ async def test_setup( ) -> None: """Test setup.""" recorder_helper.async_initialize_recorder(hass) + # default_config needs the homeassistant integration, assert it will be + # automatically setup by bootstrap and set it up manually for this test + assert "homeassistant" in bootstrap.CORE_INTEGRATIONS + assert await async_setup_component(hass, "homeassistant", {"foo": "bar"}) + assert await async_setup_component(hass, "default_config", {"foo": "bar"}) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 4c350168d4e..b640d59df44 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -87,12 +87,21 @@ async def test_async_enable_logging( async def test_load_hassio(hass: HomeAssistant) -> None: - """Test that we load Hass.io component.""" + """Test that we load the hassio integration when using Supervisor.""" with patch.dict(os.environ, {}, clear=True): - assert bootstrap._get_domains(hass, {}) == set() + assert "hassio" not in bootstrap._get_domains(hass, {}) with patch.dict(os.environ, {"SUPERVISOR": "1"}): - assert bootstrap._get_domains(hass, {}) == {"hassio"} + assert "hassio" in bootstrap._get_domains(hass, {}) + + +async def test_load_backup(hass: HomeAssistant) -> None: + """Test that we load the backup integration when not using Supervisor.""" + with patch.dict(os.environ, {}, clear=True): + assert "backup" in bootstrap._get_domains(hass, {}) + + with patch.dict(os.environ, {"SUPERVISOR": "1"}): + assert "backup" not in bootstrap._get_domains(hass, {}) @pytest.mark.parametrize("load_registries", [False]) @@ -784,6 +793,7 @@ async def test_setup_recovery_mode_if_no_frontend( @pytest.mark.parametrize("load_registries", [False]) +@patch("homeassistant.bootstrap.DEFAULT_INTEGRATIONS", set()) async def test_empty_integrations_list_is_only_sent_at_the_end_of_bootstrap( hass: HomeAssistant, ) -> None: @@ -836,7 +846,7 @@ async def test_empty_integrations_list_is_only_sent_at_the_end_of_bootstrap( assert integrations[0] != {} assert "an_after_dep" in integrations[0] - assert integrations[-3] != {} + assert integrations[-2] != {} assert integrations[-1] == {} assert "normal_integration" in hass.config.components