Add support for subscribing to config entry changes (#77803)
This commit is contained in:
parent
804e4ab989
commit
016a59ac94
3 changed files with 416 additions and 28 deletions
|
@ -19,6 +19,7 @@ from .const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, Platfo
|
|||
from .core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback
|
||||
from .exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady, HomeAssistantError
|
||||
from .helpers import device_registry, entity_registry, storage
|
||||
from .helpers.dispatcher import async_dispatcher_send
|
||||
from .helpers.event import async_call_later
|
||||
from .helpers.frame import report
|
||||
from .helpers.typing import UNDEFINED, ConfigType, DiscoveryInfoType, UndefinedType
|
||||
|
@ -136,6 +137,16 @@ RECONFIGURE_NOTIFICATION_ID = "config_entry_reconfigure"
|
|||
|
||||
EVENT_FLOW_DISCOVERED = "config_entry_discovered"
|
||||
|
||||
SIGNAL_CONFIG_ENTRY_CHANGED = "config_entry_changed"
|
||||
|
||||
|
||||
class ConfigEntryChange(StrEnum):
|
||||
"""What was changed in a config entry."""
|
||||
|
||||
ADDED = "added"
|
||||
REMOVED = "removed"
|
||||
UPDATED = "updated"
|
||||
|
||||
|
||||
class ConfigEntryDisabler(StrEnum):
|
||||
"""What disabled a config entry."""
|
||||
|
@ -310,7 +321,7 @@ class ConfigEntry:
|
|||
|
||||
# Only store setup result as state if it was not forwarded.
|
||||
if self.domain == integration.domain:
|
||||
self.state = ConfigEntryState.SETUP_IN_PROGRESS
|
||||
self.async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
|
||||
|
||||
self.supports_unload = await support_entry_unload(hass, self.domain)
|
||||
self.supports_remove_device = await support_remove_from_device(
|
||||
|
@ -327,8 +338,7 @@ class ConfigEntry:
|
|||
err,
|
||||
)
|
||||
if self.domain == integration.domain:
|
||||
self.state = ConfigEntryState.SETUP_ERROR
|
||||
self.reason = "Import error"
|
||||
self.async_set_state(hass, ConfigEntryState.SETUP_ERROR, "Import error")
|
||||
return
|
||||
|
||||
if self.domain == integration.domain:
|
||||
|
@ -341,14 +351,12 @@ class ConfigEntry:
|
|||
self.domain,
|
||||
err,
|
||||
)
|
||||
self.state = ConfigEntryState.SETUP_ERROR
|
||||
self.reason = "Import error"
|
||||
self.async_set_state(hass, ConfigEntryState.SETUP_ERROR, "Import error")
|
||||
return
|
||||
|
||||
# Perform migration
|
||||
if not await self.async_migrate(hass):
|
||||
self.state = ConfigEntryState.MIGRATION_ERROR
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.MIGRATION_ERROR, None)
|
||||
return
|
||||
|
||||
error_reason = None
|
||||
|
@ -378,8 +386,7 @@ class ConfigEntry:
|
|||
self.async_start_reauth(hass)
|
||||
result = False
|
||||
except ConfigEntryNotReady as ex:
|
||||
self.state = ConfigEntryState.SETUP_RETRY
|
||||
self.reason = str(ex) or None
|
||||
self.async_set_state(hass, ConfigEntryState.SETUP_RETRY, str(ex) or None)
|
||||
wait_time = 2 ** min(tries, 4) * 5
|
||||
tries += 1
|
||||
message = str(ex)
|
||||
|
@ -427,11 +434,9 @@ class ConfigEntry:
|
|||
return
|
||||
|
||||
if result:
|
||||
self.state = ConfigEntryState.LOADED
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.LOADED, None)
|
||||
else:
|
||||
self.state = ConfigEntryState.SETUP_ERROR
|
||||
self.reason = error_reason
|
||||
self.async_set_state(hass, ConfigEntryState.SETUP_ERROR, error_reason)
|
||||
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Call when Home Assistant is stopping."""
|
||||
|
@ -452,8 +457,7 @@ class ConfigEntry:
|
|||
Returns if unload is possible and was successful.
|
||||
"""
|
||||
if self.source == SOURCE_IGNORE:
|
||||
self.state = ConfigEntryState.NOT_LOADED
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.NOT_LOADED, None)
|
||||
return True
|
||||
|
||||
if self.state == ConfigEntryState.NOT_LOADED:
|
||||
|
@ -467,8 +471,7 @@ class ConfigEntry:
|
|||
# that was uninstalled, or an integration
|
||||
# that has been renamed without removing the config
|
||||
# entry.
|
||||
self.state = ConfigEntryState.NOT_LOADED
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.NOT_LOADED, None)
|
||||
return True
|
||||
|
||||
component = integration.get_component()
|
||||
|
@ -479,17 +482,16 @@ class ConfigEntry:
|
|||
|
||||
if self.state is not ConfigEntryState.LOADED:
|
||||
self.async_cancel_retry_setup()
|
||||
|
||||
self.state = ConfigEntryState.NOT_LOADED
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.NOT_LOADED, None)
|
||||
return True
|
||||
|
||||
supports_unload = hasattr(component, "async_unload_entry")
|
||||
|
||||
if not supports_unload:
|
||||
if integration.domain == self.domain:
|
||||
self.state = ConfigEntryState.FAILED_UNLOAD
|
||||
self.reason = "Unload not supported"
|
||||
self.async_set_state(
|
||||
hass, ConfigEntryState.FAILED_UNLOAD, "Unload not supported"
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
@ -499,20 +501,20 @@ class ConfigEntry:
|
|||
|
||||
# Only adjust state if we unloaded the component
|
||||
if result and integration.domain == self.domain:
|
||||
self.state = ConfigEntryState.NOT_LOADED
|
||||
self.reason = None
|
||||
self.async_set_state(hass, ConfigEntryState.NOT_LOADED, None)
|
||||
|
||||
await self._async_process_on_unload()
|
||||
|
||||
# https://github.com/python/mypy/issues/11839
|
||||
return result # type: ignore[no-any-return]
|
||||
except Exception: # pylint: disable=broad-except
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
_LOGGER.exception(
|
||||
"Error unloading entry %s for %s", self.title, integration.domain
|
||||
)
|
||||
if integration.domain == self.domain:
|
||||
self.state = ConfigEntryState.FAILED_UNLOAD
|
||||
self.reason = "Unknown error"
|
||||
self.async_set_state(
|
||||
hass, ConfigEntryState.FAILED_UNLOAD, str(ex) or "Unknown error"
|
||||
)
|
||||
return False
|
||||
|
||||
async def async_remove(self, hass: HomeAssistant) -> None:
|
||||
|
@ -541,6 +543,17 @@ class ConfigEntry:
|
|||
integration.domain,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_set_state(
|
||||
self, hass: HomeAssistant, state: ConfigEntryState, reason: str | None
|
||||
) -> None:
|
||||
"""Set the state of the config entry."""
|
||||
self.state = state
|
||||
self.reason = reason
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_CONFIG_ENTRY_CHANGED, ConfigEntryChange.UPDATED, self
|
||||
)
|
||||
|
||||
async def async_migrate(self, hass: HomeAssistant) -> bool:
|
||||
"""Migrate an entry.
|
||||
|
||||
|
@ -895,6 +908,7 @@ class ConfigEntries:
|
|||
)
|
||||
self._entries[entry.entry_id] = entry
|
||||
self._domain_index.setdefault(entry.domain, []).append(entry.entry_id)
|
||||
self._async_dispatch(ConfigEntryChange.ADDED, entry)
|
||||
await self.async_setup(entry.entry_id)
|
||||
self._async_schedule_save()
|
||||
|
||||
|
@ -950,6 +964,7 @@ class ConfigEntries:
|
|||
)
|
||||
)
|
||||
|
||||
self._async_dispatch(ConfigEntryChange.REMOVED, entry)
|
||||
return {"require_restart": not unload_success}
|
||||
|
||||
async def _async_shutdown(self, event: Event) -> None:
|
||||
|
@ -1161,9 +1176,18 @@ class ConfigEntries:
|
|||
self.hass.async_create_task(listener(self.hass, entry))
|
||||
|
||||
self._async_schedule_save()
|
||||
|
||||
self._async_dispatch(ConfigEntryChange.UPDATED, entry)
|
||||
return True
|
||||
|
||||
@callback
|
||||
def _async_dispatch(
|
||||
self, change_type: ConfigEntryChange, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Dispatch a config entry change."""
|
||||
async_dispatcher_send(
|
||||
self.hass, SIGNAL_CONFIG_ENTRY_CHANGED, change_type, entry
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_setup_platforms(
|
||||
self, entry: ConfigEntry, platforms: Iterable[Platform | str]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue