Changed config entry state transitions
Config entry state transitions when unloading and removing entries has been modified:
-
A new state
ConfigEntryState.UNLOAD_IN_PROGRESSis added, which is set before calling the integration'sasync_unload_entry
Rationale:- Make it easier to write cleanup code which should run after the last config entry has been unloaded
- Improve debugging of issues related to reload and unload of config entries
-
The config entry state is set to
ConfigEntryState.FAILED_UNLOADwhen the integration'sasync_unload_entryreturns False
Rationale:- If
async_unload_entryreturnsFalse, we can't assume the integration is still loaded, most likely it has partially unloaded itself, especially considering this is the pattern we recommend:
Pythonasync def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool: """Unload a config entry.""" # async_unload_platforms returns False if at least one platform did not unload if (unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS)): entry.runtime_data.listener() # Finish cleanup not related to platforms return unload_ok - If
-
The config entry is removed from
hass.config_entriesbefore calling the integration'sasync_remove_entryis called
Rationale:- Make it easier to write cleanup code which should run after the last config entry has been removed
Custom integration authors need to review and update their integrations' async_unload_entry and async_remove_entry if needed.
The most common pattern which requires an update is this:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...This can now be simplified, if the custom integration's minimum Home Assistant version is set to 2025.3.0:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
# The last config entry is being unloaded, release shared resources, unregister services etc.
...If the custom integration needs to be backwards compatible with previous releases of Home Assistant Core:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
other_loaded_entries = [
_entry
for _entry in hass.config_entries.async_loaded_entries(DOMAIN)
if _entry.entry_id != entry.entry_id
]
if not other_loaded_entries:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...Check the config entry documentation, and the home assistant core PR #138522 for additional background.