mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Add support for raising ConfigEntryError (#82689)
This commit is contained in:
parent
e7d4f745ec
commit
c715035016
@ -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:
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
@ -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"
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user