Add support for raising ConfigEntryError (#82689)

This commit is contained in:
Franck Nijhof 2022-11-25 11:33:03 +01:00 committed by GitHub
parent e7d4f745ec
commit c715035016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 4 deletions

View File

@ -70,12 +70,14 @@ class Coordinator(DataUpdateCoordinator[State]):
log_failures: bool = True, log_failures: bool = True,
raise_on_auth_failed: bool = False, raise_on_auth_failed: bool = False,
scheduled: bool = False, scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None: ) -> None:
self._refresh_was_scheduled = scheduled self._refresh_was_scheduled = scheduled
await super()._async_refresh( await super()._async_refresh(
log_failures=log_failures, log_failures=log_failures,
raise_on_auth_failed=raise_on_auth_failed, raise_on_auth_failed=raise_on_auth_failed,
scheduled=scheduled, scheduled=scheduled,
raise_on_entry_error=raise_on_entry_error,
) )
async def _async_update_data(self) -> State: async def _async_update_data(self) -> State:

View File

@ -866,6 +866,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
log_failures: bool = True, log_failures: bool = True,
raise_on_auth_failed: bool = False, raise_on_auth_failed: bool = False,
scheduled: bool = False, scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None: ) -> None:
"""Refresh data.""" """Refresh data."""
if not scheduled: if not scheduled:
@ -874,4 +875,6 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
await self.hassio.refresh_updates() await self.hassio.refresh_updates()
except HassioAPIError as err: except HassioAPIError as err:
_LOGGER.warning("Error on Supervisor API: %s", err) _LOGGER.warning("Error on Supervisor API: %s", err)
await super()._async_refresh(log_failures, raise_on_auth_failed, scheduled) await super()._async_refresh(
log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error
)

View File

@ -19,7 +19,12 @@ from .components import persistent_notification
from .const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, Platform from .const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, Platform
from .core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback from .core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback
from .data_entry_flow import FlowResult from .data_entry_flow import FlowResult
from .exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady, HomeAssistantError from .exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
HomeAssistantError,
)
from .helpers import device_registry, entity_registry, storage from .helpers import device_registry, entity_registry, storage
from .helpers.dispatcher import async_dispatcher_send from .helpers.dispatcher import async_dispatcher_send
from .helpers.event import async_call_later from .helpers.event import async_call_later
@ -371,6 +376,16 @@ class ConfigEntry:
"%s.async_setup_entry did not return boolean", integration.domain "%s.async_setup_entry did not return boolean", integration.domain
) )
result = False result = False
except ConfigEntryError as ex:
error_reason = str(ex) or "Unknown fatal config entry error"
_LOGGER.exception(
"Error setting up entry %s for %s: %s",
self.title,
self.domain,
error_reason,
)
await self._async_process_on_unload()
result = False
except ConfigEntryAuthFailed as ex: except ConfigEntryAuthFailed as ex:
message = str(ex) message = str(ex)
auth_base_message = "could not authenticate" auth_base_message = "could not authenticate"

View File

@ -114,6 +114,10 @@ class PlatformNotReady(IntegrationError):
"""Error to indicate that platform is not ready.""" """Error to indicate that platform is not ready."""
class ConfigEntryError(IntegrationError):
"""Error to indicate that config entry setup has failed."""
class ConfigEntryNotReady(IntegrationError): class ConfigEntryNotReady(IntegrationError):
"""Error to indicate that config entry is not ready.""" """Error to indicate that config entry is not ready."""

View File

@ -15,7 +15,11 @@ import requests
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
)
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from . import entity, event from . import entity, event
@ -183,7 +187,9 @@ class DataUpdateCoordinator(Generic[_T]):
fails. Additionally logging is handled by config entry setup fails. Additionally logging is handled by config entry setup
to ensure that multiple retries do not cause log spam. to ensure that multiple retries do not cause log spam.
""" """
await self._async_refresh(log_failures=False, raise_on_auth_failed=True) await self._async_refresh(
log_failures=False, raise_on_auth_failed=True, raise_on_entry_error=True
)
if self.last_update_success: if self.last_update_success:
return return
ex = ConfigEntryNotReady() ex = ConfigEntryNotReady()
@ -199,6 +205,7 @@ class DataUpdateCoordinator(Generic[_T]):
log_failures: bool = True, log_failures: bool = True,
raise_on_auth_failed: bool = False, raise_on_auth_failed: bool = False,
scheduled: bool = False, scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None: ) -> None:
"""Refresh data.""" """Refresh data."""
if self._unsub_refresh: if self._unsub_refresh:
@ -250,6 +257,19 @@ class DataUpdateCoordinator(Generic[_T]):
self.logger.error("Error fetching %s data: %s", self.name, err) self.logger.error("Error fetching %s data: %s", self.name, err)
self.last_update_success = False self.last_update_success = False
except ConfigEntryError as err:
self.last_exception = err
if self.last_update_success:
if log_failures:
self.logger.error(
"Config entry setup failed while fetching %s data: %s",
self.name,
err,
)
self.last_update_success = False
if raise_on_entry_error:
raise
except ConfigEntryAuthFailed as err: except ConfigEntryAuthFailed as err:
auth_failed = True auth_failed = True
self.last_exception = err self.last_exception = err

View File

@ -20,6 +20,7 @@ from homeassistant.core import CoreState, Event, HomeAssistant, callback
from homeassistant.data_entry_flow import BaseServiceInfo, FlowResult, FlowResultType from homeassistant.data_entry_flow import BaseServiceInfo, FlowResult, FlowResultType
from homeassistant.exceptions import ( from homeassistant.exceptions import (
ConfigEntryAuthFailed, ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady, ConfigEntryNotReady,
HomeAssistantError, HomeAssistantError,
) )
@ -2866,6 +2867,96 @@ async def test_entry_reload_calls_on_unload_listeners(hass, manager):
assert entry.state is config_entries.ConfigEntryState.LOADED assert entry.state is config_entries.ConfigEntryState.LOADED
async def test_setup_raise_entry_error(hass, caplog):
"""Test a setup raising ConfigEntryError."""
entry = MockConfigEntry(title="test_title", domain="test")
mock_setup_entry = AsyncMock(
side_effect=ConfigEntryError("Incompatible firmware version")
)
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
mock_entity_platform(hass, "config_flow.test", None)
await entry.async_setup(hass)
await hass.async_block_till_done()
assert (
"Error setting up entry test_title for test: Incompatible firmware version"
in caplog.text
)
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
assert entry.reason == "Incompatible firmware version"
async def test_setup_raise_entry_error_from_first_coordinator_update(hass, caplog):
"""Test async_config_entry_first_refresh raises ConfigEntryError."""
entry = MockConfigEntry(title="test_title", domain="test")
async def async_setup_entry(hass, entry):
"""Mock setup entry with a simple coordinator."""
async def _async_update_data():
raise ConfigEntryError("Incompatible firmware version")
coordinator = DataUpdateCoordinator(
hass,
logging.getLogger(__name__),
name="any",
update_method=_async_update_data,
update_interval=timedelta(seconds=1000),
)
await coordinator.async_config_entry_first_refresh()
return True
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
mock_entity_platform(hass, "config_flow.test", None)
await entry.async_setup(hass)
await hass.async_block_till_done()
assert (
"Error setting up entry test_title for test: Incompatible firmware version"
in caplog.text
)
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
assert entry.reason == "Incompatible firmware version"
async def test_setup_not_raise_entry_error_from_future_coordinator_update(hass, caplog):
"""Test a coordinator not raises ConfigEntryError in the future."""
entry = MockConfigEntry(title="test_title", domain="test")
async def async_setup_entry(hass, entry):
"""Mock setup entry with a simple coordinator."""
async def _async_update_data():
raise ConfigEntryError("Incompatible firmware version")
coordinator = DataUpdateCoordinator(
hass,
logging.getLogger(__name__),
name="any",
update_method=_async_update_data,
update_interval=timedelta(seconds=1000),
)
await coordinator.async_refresh()
return True
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
mock_entity_platform(hass, "config_flow.test", None)
await entry.async_setup(hass)
await hass.async_block_till_done()
assert (
"Config entry setup failed while fetching any data: Incompatible firmware version"
in caplog.text
)
assert entry.state is config_entries.ConfigEntryState.LOADED
async def test_setup_raise_auth_failed(hass, caplog): async def test_setup_raise_auth_failed(hass, caplog):
"""Test a setup raising ConfigEntryAuthFailed.""" """Test a setup raising ConfigEntryAuthFailed."""
entry = MockConfigEntry(title="test_title", domain="test") entry = MockConfigEntry(title="test_title", domain="test")