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,
raise_on_auth_failed: bool = False,
scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None:
self._refresh_was_scheduled = scheduled
await super()._async_refresh(
log_failures=log_failures,
raise_on_auth_failed=raise_on_auth_failed,
scheduled=scheduled,
raise_on_entry_error=raise_on_entry_error,
)
async def _async_update_data(self) -> State:

View file

@ -866,6 +866,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
log_failures: bool = True,
raise_on_auth_failed: bool = False,
scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None:
"""Refresh data."""
if not scheduled:
@ -874,4 +875,6 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
await self.hassio.refresh_updates()
except HassioAPIError as 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 .core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback
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.dispatcher import async_dispatcher_send
from .helpers.event import async_call_later
@ -371,6 +376,16 @@ class ConfigEntry:
"%s.async_setup_entry did not return boolean", integration.domain
)
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:
message = str(ex)
auth_base_message = "could not authenticate"

View file

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

View file

@ -15,7 +15,11 @@ import requests
from homeassistant import config_entries
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 . import entity, event
@ -183,7 +187,9 @@ class DataUpdateCoordinator(Generic[_T]):
fails. Additionally logging is handled by config entry setup
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:
return
ex = ConfigEntryNotReady()
@ -199,6 +205,7 @@ class DataUpdateCoordinator(Generic[_T]):
log_failures: bool = True,
raise_on_auth_failed: bool = False,
scheduled: bool = False,
raise_on_entry_error: bool = False,
) -> None:
"""Refresh data."""
if self._unsub_refresh:
@ -250,6 +257,19 @@ class DataUpdateCoordinator(Generic[_T]):
self.logger.error("Error fetching %s data: %s", self.name, err)
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:
auth_failed = True
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.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
HomeAssistantError,
)
@ -2866,6 +2867,96 @@ async def test_entry_reload_calls_on_unload_listeners(hass, manager):
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):
"""Test a setup raising ConfigEntryAuthFailed."""
entry = MockConfigEntry(title="test_title", domain="test")