Add support for raising ConfigEntryError (#82689)
This commit is contained in:
parent
e7d4f745ec
commit
c715035016
6 changed files with 139 additions and 4 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Reference in a new issue