From 7a009ed6cdef06cfe1d3d9559f19b2c4748465a5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 23 Oct 2023 20:26:56 +0200 Subject: [PATCH] Don't duplicate core services in hassio (#102593) --- homeassistant/components/hassio/__init__.py | 69 +++---------------- .../components/homeassistant/__init__.py | 41 ++++++++--- .../components/homeassistant/const.py | 6 ++ homeassistant/const.py | 3 - tests/components/hassio/test_init.py | 1 + tests/components/homeassistant/test_init.py | 4 +- 6 files changed, 51 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 91b87416c15..78e9c40cebd 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -13,25 +13,18 @@ from typing import Any, NamedTuple import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN -from homeassistant.components import panel_custom, persistent_notification -from homeassistant.components.homeassistant import ( - SERVICE_CHECK_CONFIG, - SHUTDOWN_SERVICES, -) -import homeassistant.config as conf_util +from homeassistant.components import panel_custom +from homeassistant.components.homeassistant import async_set_stop_handler from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_MANUFACTURER, ATTR_NAME, EVENT_CORE_CONFIG_UPDATE, HASSIO_USER_NAME, - SERVICE_HOMEASSISTANT_RESTART, - SERVICE_HOMEASSISTANT_STOP, Platform, ) from homeassistant.core import ( CALLBACK_TYPE, - DOMAIN as HASS_DOMAIN, HassJob, HomeAssistant, ServiceCall, @@ -39,11 +32,7 @@ from homeassistant.core import ( callback, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import ( - config_validation as cv, - device_registry as dr, - recorder, -) +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.event import async_call_later @@ -573,53 +562,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: # Fetch data await update_info_data() - async def async_handle_core_service(call: ServiceCall) -> None: - """Service handler for handling core services.""" - if call.service in SHUTDOWN_SERVICES and recorder.async_migration_in_progress( - hass - ): - _LOGGER.error( - "The system cannot %s while a database upgrade is in progress", - call.service, - ) - raise HomeAssistantError( - f"The system cannot {call.service} " - "while a database upgrade is in progress." - ) - - if call.service == SERVICE_HOMEASSISTANT_STOP: - await hassio.stop_homeassistant() - return - - errors = await conf_util.async_check_ha_config_file(hass) - - if errors: - _LOGGER.error( - "The system cannot %s because the configuration is not valid: %s", - call.service, - errors, - ) - persistent_notification.async_create( - hass, - "Config error. See [the logs](/config/logs) for details.", - "Config validating", - f"{HASS_DOMAIN}.check_config", - ) - raise HomeAssistantError( - f"The system cannot {call.service} " - f"because the configuration is not valid: {errors}" - ) - - if call.service == SERVICE_HOMEASSISTANT_RESTART: + async def _async_stop(hass: HomeAssistant, restart: bool) -> None: + """Stop or restart home assistant.""" + if restart: await hassio.restart_homeassistant() + else: + await hassio.stop_homeassistant() - # Mock core services - for service in ( - SERVICE_HOMEASSISTANT_STOP, - SERVICE_HOMEASSISTANT_RESTART, - SERVICE_CHECK_CONFIG, - ): - hass.services.async_register(HASS_DOMAIN, service, async_handle_core_service) + # Set a custom handler for the homeassistant.restart and homeassistant.stop services + async_set_stop_handler(hass, _async_stop) # Init discovery Hass.io feature async_setup_discovery_view(hass, hassio) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index e4032ad954d..5b26cb29ded 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -1,7 +1,9 @@ """Integration providing core pieces of infrastructure.""" import asyncio +from collections.abc import Callable, Coroutine import itertools as it import logging +from typing import Any import voluptuous as vol @@ -14,8 +16,6 @@ from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, RESTART_EXIT_CODE, - SERVICE_HOMEASSISTANT_RESTART, - SERVICE_HOMEASSISTANT_STOP, SERVICE_RELOAD, SERVICE_SAVE_PERSISTENT_STATES, SERVICE_TOGGLE, @@ -34,7 +34,13 @@ from homeassistant.helpers.service import ( from homeassistant.helpers.template import async_load_custom_templates from homeassistant.helpers.typing import ConfigType -from .const import DATA_EXPOSED_ENTITIES, DOMAIN +from .const import ( + DATA_EXPOSED_ENTITIES, + DATA_STOP_HANDLER, + DOMAIN, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_HOMEASSISTANT_STOP, +) from .exposed_entities import ExposedEntities ATTR_ENTRY_ID = "entry_id" @@ -148,6 +154,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no async def async_handle_core_service(call: ha.ServiceCall) -> None: """Service handler for handling core services.""" + stop_handler: Callable[[ha.HomeAssistant, bool], Coroutine[Any, Any, None]] + if call.service in SHUTDOWN_SERVICES and recorder.async_migration_in_progress( hass ): @@ -161,8 +169,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no ) if call.service == SERVICE_HOMEASSISTANT_STOP: - # Track trask in hass.data. No need to cleanup, we're stopping. - hass.data["homeassistant_stop"] = asyncio.create_task(hass.async_stop()) + stop_handler = hass.data[DATA_STOP_HANDLER] + await stop_handler(hass, False) return errors = await conf_util.async_check_ha_config_file(hass) @@ -185,10 +193,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no ) if call.service == SERVICE_HOMEASSISTANT_RESTART: - # Track trask in hass.data. No need to cleanup, we're stopping. - hass.data["homeassistant_stop"] = asyncio.create_task( - hass.async_stop(RESTART_EXIT_CODE) - ) + stop_handler = hass.data[DATA_STOP_HANDLER] + await stop_handler(hass, True) async def async_handle_update_service(call: ha.ServiceCall) -> None: """Service handler for updating an entity.""" @@ -358,5 +364,22 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no exposed_entities = ExposedEntities(hass) await exposed_entities.async_initialize() hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities + async_set_stop_handler(hass, _async_stop) return True + + +async def _async_stop(hass: ha.HomeAssistant, restart: bool): + """Stop home assistant.""" + exit_code = RESTART_EXIT_CODE if restart else 0 + # Track trask in hass.data. No need to cleanup, we're stopping. + hass.data["homeassistant_stop"] = asyncio.create_task(hass.async_stop(exit_code)) + + +@ha.callback +def async_set_stop_handler( + hass: ha.HomeAssistant, + stop_handler: Callable[[ha.HomeAssistant, bool], Coroutine[Any, Any, None]], +) -> None: + """Set function which is called by the stop and restart services.""" + hass.data[DATA_STOP_HANDLER] = stop_handler diff --git a/homeassistant/components/homeassistant/const.py b/homeassistant/components/homeassistant/const.py index f3bc95dd1ee..871ea5a0371 100644 --- a/homeassistant/components/homeassistant/const.py +++ b/homeassistant/components/homeassistant/const.py @@ -1,6 +1,12 @@ """Constants for the Homeassistant integration.""" +from typing import Final + import homeassistant.core as ha DOMAIN = ha.DOMAIN DATA_EXPOSED_ENTITIES = f"{DOMAIN}.exposed_entites" +DATA_STOP_HANDLER = f"{DOMAIN}.stop_handler" + +SERVICE_HOMEASSISTANT_STOP: Final = "stop" +SERVICE_HOMEASSISTANT_RESTART: Final = "restart" diff --git a/homeassistant/const.py b/homeassistant/const.py index 2d84a57afa4..d44ec25230f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1058,9 +1058,6 @@ COMPRESSED_STATE_LAST_CHANGED = "lc" COMPRESSED_STATE_LAST_UPDATED = "lu" # #### SERVICES #### -SERVICE_HOMEASSISTANT_STOP: Final = "stop" -SERVICE_HOMEASSISTANT_RESTART: Final = "restart" - SERVICE_TURN_ON: Final = "turn_on" SERVICE_TURN_OFF: Final = "turn_off" SERVICE_TOGGLE: Final = "toggle" diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index c04a26638e6..99e1de6e763 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -692,6 +692,7 @@ async def test_service_calls_core( hassio_env, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Call core service and check the API calls behind that.""" + assert await async_setup_component(hass, "homeassistant", {}) assert await async_setup_component(hass, "hassio", {}) aioclient_mock.post("http://127.0.0.1/homeassistant/restart", json={"result": "ok"}) diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 4c5643ae3ca..9048e03ea70 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -12,6 +12,8 @@ import homeassistant.components as comps from homeassistant.components.homeassistant import ( ATTR_ENTRY_ID, SERVICE_CHECK_CONFIG, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_HOMEASSISTANT_STOP, SERVICE_RELOAD_ALL, SERVICE_RELOAD_CORE_CONFIG, SERVICE_RELOAD_CUSTOM_TEMPLATES, @@ -22,8 +24,6 @@ from homeassistant.const import ( ENTITY_MATCH_ALL, ENTITY_MATCH_NONE, EVENT_CORE_CONFIG_UPDATE, - SERVICE_HOMEASSISTANT_RESTART, - SERVICE_HOMEASSISTANT_STOP, SERVICE_SAVE_PERSISTENT_STATES, SERVICE_TOGGLE, SERVICE_TURN_OFF,