async def async_unload( self, hass: HomeAssistant, *, integration: loader.Integration | None = None) -> bool: """Unload an entry. Returns if unload is possible and was successful. """ if self.source == SOURCE_IGNORE: self.state = ENTRY_STATE_NOT_LOADED return True if integration is None: try: integration = await loader.async_get_integration( hass, self.domain) except loader.IntegrationNotFound: # The integration was likely a custom_component # that was uninstalled, or an integration # that has been renamed without removing the config # entry. self.state = ENTRY_STATE_NOT_LOADED return True component = integration.get_component() if integration.domain == self.domain: if self.state in UNRECOVERABLE_STATES: return False if self.state != ENTRY_STATE_LOADED: if self._async_cancel_retry_setup is not None: self._async_cancel_retry_setup() self._async_cancel_retry_setup = None self.state = ENTRY_STATE_NOT_LOADED return True supports_unload = hasattr(component, "async_unload_entry") if not supports_unload: if integration.domain == self.domain: self.state = ENTRY_STATE_FAILED_UNLOAD return False try: result = await component.async_unload_entry(hass, self) # type: ignore assert isinstance(result, bool) # Only adjust state if we unloaded the component if result and integration.domain == self.domain: self.state = ENTRY_STATE_NOT_LOADED return result except Exception: # pylint: disable=broad-except _LOGGER.exception("Error unloading entry %s for %s", self.title, integration.domain) if integration.domain == self.domain: self.state = ENTRY_STATE_FAILED_UNLOAD return False
async def async_setup( self, hass: HomeAssistant, *, integration: loader.Integration | None = None, tries: int = 0, ) -> None: """Set up an entry.""" if self.source == SOURCE_IGNORE or self.disabled_by: return if integration is None: integration = await loader.async_get_integration(hass, self.domain) self.supports_unload = await support_entry_unload(hass, self.domain) try: component = integration.get_component() except ImportError as err: _LOGGER.error( "Error importing integration %s to set up %s configuration entry: %s", integration.domain, self.domain, err, ) if self.domain == integration.domain: self.state = ENTRY_STATE_SETUP_ERROR return if self.domain == integration.domain: try: integration.get_platform("config_flow") except ImportError as err: _LOGGER.error( "Error importing platform config_flow from integration %s to set up %s configuration entry: %s", integration.domain, self.domain, err, ) self.state = ENTRY_STATE_SETUP_ERROR return # Perform migration if not await self.async_migrate(hass): self.state = ENTRY_STATE_MIGRATION_ERROR return try: result = await component.async_setup_entry(hass, self) # type: ignore if not isinstance(result, bool): _LOGGER.error("%s.async_setup_entry did not return boolean", integration.domain) result = False except ConfigEntryNotReady as ex: self.state = ENTRY_STATE_SETUP_RETRY wait_time = 2**min(tries, 4) * 5 tries += 1 message = str(ex) if not message and ex.__cause__: message = str(ex.__cause__) ready_message = f"ready yet: {message}" if message else "ready yet" if tries == 1: _LOGGER.warning( "Config entry '%s' for %s integration not %s; Retrying in background", self.title, self.domain, ready_message, ) else: _LOGGER.debug( "Config entry '%s' for %s integration not %s; Retrying in %d seconds", self.title, self.domain, ready_message, wait_time, ) async def setup_again(*_: Any) -> None: """Run setup again.""" self._async_cancel_retry_setup = None await self.async_setup(hass, integration=integration, tries=tries) if hass.state == CoreState.running: self._async_cancel_retry_setup = hass.helpers.event.async_call_later( wait_time, setup_again) else: self._async_cancel_retry_setup = hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STARTED, setup_again) return except Exception: # pylint: disable=broad-except _LOGGER.exception("Error setting up entry %s for %s", self.title, integration.domain) result = False # Only store setup result as state if it was not forwarded. if self.domain != integration.domain: return if result: self.state = ENTRY_STATE_LOADED else: self.state = ENTRY_STATE_SETUP_ERROR