diff --git a/docs/core/platform/backup.md b/docs/core/platform/backup.md index 25852f89..559c5959 100644 --- a/docs/core/platform/backup.md +++ b/docs/core/platform/backup.md @@ -2,11 +2,133 @@ title: "Backup" --- +There are two main purposes for an integration to implement a backup platform: + +1. Add a backup agent that can upload backups to some local or remote location. +2. Pause or prepare integration operations before creating a backup and/or run some operation after a backup. + +## Backup Agents + +To add one or more backup agents, implement the two methods, `async_get_backup_agents` and `async_register_backup_agents_listener` in `backup.py`. Example: + +```python +async def async_get_backup_agents( + hass: HomeAssistant, +) -> list[BackupAgent]: + """Return a list of backup agents.""" + if not hass.config_entries.async_loaded_entries(DOMAIN): + LOGGER.debug("No config entry found or entry is not loaded") + return [] + return [ExampleBackupAgent()] + + +@callback +def async_register_backup_agents_listener( + hass: HomeAssistant, + *, + listener: Callable[[], None], + **kwargs: Any, +) -> Callable[[], None]: + """Register a listener to be called when agents are added or removed. + + :return: A function to unregister the listener. + """ + hass.data.setdefault(DATA_BACKUP_AGENT_LISTENERS, []).append(listener) + + @callback + def remove_listener() -> None: + """Remove the listener.""" + hass.data[DATA_BACKUP_AGENT_LISTENERS].remove(listener) + + return remove_listener +``` + +The listener stored in `async_register_backup_agents_listener` should be called every time there is the need to reload backup agents, to remove stale agents and add new ones, such as when the integration is reloaded. For example: + +```python +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload config entry.""" + # Notify backup listeners + hass.async_create_task(_notify_backup_listeners(hass), eager_start=False) + + return await hass.config_entries.async_unload_platforms( + entry, PLATFORMS + ) + + +async def _notify_backup_listeners(hass: HomeAssistant) -> None: + for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []): + listener() +``` + +A backup agent should implement the abstract interface of the `BackupAgent` base class as shown in this example: + +```python +from homeassistant.components.backup import BackupAgent, BackupAgentError + +from .const import DOMAIN + + +class ExampleBackupAgent(BackupAgent): + """Backup agent interface.""" + + domain = DOMAIN + name = "example" + + async def async_download_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> AsyncIterator[bytes]: + """Download a backup file. + + :param backup_id: The ID of the backup that was returned in async_list_backups. + :return: An async iterator that yields bytes. + """ + + async def async_upload_backup( + self, + *, + open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]], + backup: AgentBackup, + **kwargs: Any, + ) -> None: + """Upload a backup. + + :param open_stream: A function returning an async iterator that yields bytes. + :param backup: Metadata about the backup that should be uploaded. + """ + + async def async_delete_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> None: + """Delete a backup file. + + :param backup_id: The ID of the backup that was returned in async_list_backups. + """ + + async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]: + """List backups.""" + + async def async_get_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> AgentBackup | None: + """Return a backup.""" +``` + +Backup agents should raise a `BackupAgentError` exception on error. Other exceptions are not expected to leave the backup agent. + +## Pre- and post-operations + When Home Assistant is creating a backup, there might be a need to pause certain operations in the integration, or dumping data so it can properly be restored. -This is done by adding 2 functions (`async_pre_backup` and `async_post_backup`) to `backup.py` +This is done by adding two functions (`async_pre_backup` and `async_post_backup`) to `backup.py` -## Adding support +### Adding support The quickest way to add backup support to a new integration is by using our built-in scaffold template. From a Home Assistant dev environment, run `python3 -m script.scaffold backup` and follow the instructions.