Raise in EntityComponent.async_prepare_reload on configuration error

This commit is contained in:
Erik 2023-10-02 15:07:44 +02:00
parent 07ceafed62
commit fca50bfbf8
17 changed files with 49 additions and 43 deletions

View File

@ -333,8 +333,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all automations and load new ones from config.""" """Remove all automations and load new ones from config."""
await async_get_blueprints(hass).async_reset_cache() await async_get_blueprints(hass).async_reset_cache()
if (conf := await component.async_prepare_reload(skip_reset=True)) is None: conf = await component.async_prepare_reload(skip_reset=True)
return
if automation_id := service_call.data.get(CONF_ID): if automation_id := service_call.data.get(CONF_ID):
await _async_process_single_config(hass, conf, component, automation_id) await _async_process_single_config(hass, conf, component, automation_id)
else: else:

View File

@ -194,8 +194,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
- Remove group.group entities not created by service calls and set them up again - Remove group.group entities not created by service calls and set them up again
- Reload xxx.group platforms - Reload xxx.group platforms
""" """
if (conf := await component.async_prepare_reload(skip_reset=True)) is None: conf = await component.async_prepare_reload(skip_reset=True)
return
# Simplified + modified version of EntityPlatform.async_reset: # Simplified + modified version of EntityPlatform.async_reset:
# - group.group never retries setup # - group.group never retries setup

View File

@ -121,8 +121,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all input booleans and load new ones from config.""" """Remove all input booleans and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load( await yaml_collection.async_load(
[ [
{CONF_ID: id_, **(conf or {})} {CONF_ID: id_, **(conf or {})}

View File

@ -106,8 +106,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all input buttons and load new ones from config.""" """Remove all input buttons and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load( await yaml_collection.async_load(
[ [
{CONF_ID: id_, **(conf or {})} {CONF_ID: id_, **(conf or {})}

View File

@ -159,8 +159,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
) )

View File

@ -137,8 +137,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **conf} for id_, conf in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **conf} for id_, conf in conf.get(DOMAIN, {}).items()]
) )

View File

@ -167,8 +167,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
) )

View File

@ -137,8 +137,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **(cfg or {})} for id_, cfg in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **(cfg or {})} for id_, cfg in conf.get(DOMAIN, {}).items()]
) )

View File

@ -403,8 +403,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_reload_yaml(call: ServiceCall) -> None: async def async_reload_yaml(call: ServiceCall) -> None:
"""Reload YAML.""" """Reload YAML."""
conf = await entity_component.async_prepare_reload(skip_reset=True) conf = await entity_component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load( await yaml_collection.async_load(
await filter_yaml_data(hass, conf.get(DOMAIN, [])) await filter_yaml_data(hass, conf.get(DOMAIN, []))
) )

View File

@ -187,8 +187,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
) )

View File

@ -229,8 +229,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service(service: ServiceCall) -> None: async def reload_service(service: ServiceCall) -> None:
"""Call a service to reload scripts.""" """Call a service to reload scripts."""
await async_get_blueprints(hass).async_reset_cache() await async_get_blueprints(hass).async_reset_cache()
if (conf := await component.async_prepare_reload(skip_reset=True)) is None: conf = await component.async_prepare_reload(skip_reset=True)
return
await _async_process_config(hass, conf, component) await _async_process_config(hass, conf, component)
async def turn_on_service(service: ServiceCall) -> None: async def turn_on_service(service: ServiceCall) -> None:

View File

@ -141,8 +141,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities.""" """Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load( await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
) )

View File

@ -265,8 +265,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None: async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all zones and load new ones from config.""" """Remove all zones and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True) conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(conf[DOMAIN]) await yaml_collection.async_load(conf[DOMAIN])
service.async_register_admin_service( service.async_register_admin_service(

View File

@ -16,7 +16,7 @@ from pathlib import Path
import re import re
import shutil import shutil
from types import ModuleType from types import ModuleType
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any, Literal, overload
from urllib.parse import urlparse from urllib.parse import urlparse
from awesomeversion import AwesomeVersion from awesomeversion import AwesomeVersion
@ -1208,6 +1208,39 @@ def _get_log_message_and_stack_print_pref(
return (log_message, show_stack_trace, placeholders) return (log_message, show_stack_trace, placeholders)
# The complicated overloads are due to a limitation in mypy, details in
# https://github.com/python/mypy/issues/7333
@overload
@callback
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
) -> ConfigType | None: ...
@overload
@callback
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
*,
raise_on_failure: Literal[True],
) -> ConfigType: ...
@overload
@callback
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
*,
raise_on_failure: bool,
) -> ConfigType | None: ...
async def async_process_component_and_handle_errors( async def async_process_component_and_handle_errors(
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,
@ -1226,7 +1259,7 @@ async def async_process_component_and_handle_errors(
hass, config, integration hass, config, integration
) )
async_handle_component_errors( async_handle_component_errors(
hass, integration_config_info, integration, raise_on_failure hass, integration_config_info, integration, raise_on_failure=raise_on_failure
) )
return async_drop_config_annotations(integration_config_info, integration) return async_drop_config_annotations(integration_config_info, integration)

View File

@ -348,28 +348,19 @@ class EntityComponent(Generic[_EntityT]):
if found: if found:
await found.async_remove_entity(entity_id) await found.async_remove_entity(entity_id)
async def async_prepare_reload( async def async_prepare_reload(self, *, skip_reset: bool = False) -> ConfigType:
self, *, skip_reset: bool = False
) -> ConfigType | None:
"""Prepare reloading this entity component. """Prepare reloading this entity component.
This method must be run in the event loop. This method must be run in the event loop.
""" """
try: conf = await conf_util.async_hass_config_yaml(self.hass)
conf = await conf_util.async_hass_config_yaml(self.hass)
except HomeAssistantError as err:
self.logger.error(err)
return None
integration = await async_get_integration(self.hass, self.domain) integration = await async_get_integration(self.hass, self.domain)
processed_conf = await conf_util.async_process_component_and_handle_errors( processed_conf = await conf_util.async_process_component_and_handle_errors(
self.hass, conf, integration self.hass, conf, integration, raise_on_failure=True
) )
if processed_conf is None:
return None
if not skip_reset: if not skip_reset:
await self._async_reset() await self._async_reset()

View File

@ -136,6 +136,8 @@ async def _async_reconfig_platform(
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
# The complicated overloads are due to a limitation in mypy, details in
# https://github.com/python/mypy/issues/7333
@overload @overload
async def async_integration_yaml_config( async def async_integration_yaml_config(
hass: HomeAssistant, integration_name: str hass: HomeAssistant, integration_name: str

View File

@ -704,9 +704,12 @@ async def test_reload_config_handles_load_fails(
assert len(calls) == 1 assert len(calls) == 1
assert calls[0].data.get("event") == "test_event" assert calls[0].data.get("event") == "test_event"
with patch( with (
"homeassistant.config.load_yaml_config_file", patch(
side_effect=HomeAssistantError("bla"), "homeassistant.config.load_yaml_config_file",
side_effect=HomeAssistantError("bla"),
),
pytest.raises(HomeAssistantError),
): ):
await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True) await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True)