Rename backup slug to backup_id (#130902)

This commit is contained in:
Erik Montnemery
2024-11-18 22:24:51 +01:00
committed by GitHub
parent c7e435693e
commit 78053b487c
14 changed files with 192 additions and 216 deletions

View File

@@ -8,7 +8,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.hassio import is_hassio
from homeassistant.helpers.typing import ConfigType
from .agent import BackupAgent, BackupAgentPlatformProtocol, UploadedBackup
from .agent import BackupAgent, BackupAgentPlatformProtocol
from .const import DOMAIN, LOGGER
from .http import async_register_http_views
from .manager import Backup, BackupManager, BackupPlatformProtocol
@@ -22,7 +22,6 @@ __all__ = [
"BackupPlatformProtocol",
"BackupUploadMetadata",
"BaseBackup",
"UploadedBackup",
]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
import abc
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Protocol
@@ -23,13 +22,6 @@ class BackupAgentUnreachableError(BackupAgentError):
_message = "The backup agent is unreachable."
@dataclass(slots=True)
class UploadedBackup(BaseBackup):
"""Uploaded backup class."""
id: str
class BackupAgent(abc.ABC):
"""Backup agent interface."""
@@ -38,14 +30,14 @@ class BackupAgent(abc.ABC):
@abc.abstractmethod
async def async_download_backup(
self,
backup_id: str,
*,
id: str,
path: Path,
**kwargs: Any,
) -> None:
"""Download a backup file.
:param id: The ID of the backup that was returned in async_list_backups.
:param backup_id: The ID of the backup that was returned in async_list_backups.
:param path: The full file path to download the backup to.
"""
@@ -64,16 +56,15 @@ class BackupAgent(abc.ABC):
"""
@abc.abstractmethod
async def async_list_backups(self, **kwargs: Any) -> list[UploadedBackup]:
async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]:
"""List backups."""
@abc.abstractmethod
async def async_get_backup(
self,
*,
slug: str,
backup_id: str,
**kwargs: Any,
) -> UploadedBackup | None:
) -> BaseBackup | None:
"""Return a backup."""
@@ -81,10 +72,10 @@ class LocalBackupAgent(BackupAgent):
"""Local backup agent."""
@abc.abstractmethod
def get_backup_path(self, slug: str) -> Path:
def get_backup_path(self, backup_id: str) -> Path:
"""Return the local path to a backup.
The method should return the path to the backup file with the specified slug.
The method should return the path to the backup file with the specified id.
"""

View File

@@ -10,9 +10,9 @@ from typing import Any
from homeassistant.core import HomeAssistant
from .agent import BackupAgent, LocalBackupAgent, UploadedBackup
from .agent import BackupAgent, LocalBackupAgent
from .const import LOGGER
from .models import BackupUploadMetadata
from .models import BackupUploadMetadata, BaseBackup
from .util import read_backup
@@ -25,7 +25,7 @@ async def async_get_backup_agents(
@dataclass(slots=True)
class LocalBackup(UploadedBackup):
class LocalBackup(BaseBackup):
"""Local backup class."""
path: Path
@@ -62,23 +62,22 @@ class CoreLocalBackupAgent(LocalBackupAgent):
try:
base_backup = read_backup(backup_path)
backup = LocalBackup(
id=base_backup.slug,
slug=base_backup.slug,
backup_id=base_backup.backup_id,
name=base_backup.name,
date=base_backup.date,
path=backup_path,
size=round(backup_path.stat().st_size / 1_048_576, 2),
protected=base_backup.protected,
)
backups[backup.slug] = backup
backups[backup.backup_id] = backup
except (OSError, TarError, json.JSONDecodeError, KeyError) as err:
LOGGER.warning("Unable to read backup %s: %s", backup_path, err)
return backups
async def async_download_backup(
self,
backup_id: str,
*,
id: str,
path: Path,
**kwargs: Any,
) -> None:
@@ -93,17 +92,16 @@ class CoreLocalBackupAgent(LocalBackupAgent):
**kwargs: Any,
) -> None:
"""Upload a backup."""
self._backups[metadata.slug] = LocalBackup(
id=metadata.slug, # Do we need another ID?
slug=metadata.slug,
name=metadata.name,
self._backups[metadata.backup_id] = LocalBackup(
backup_id=metadata.backup_id,
date=metadata.date,
name=metadata.name,
path=path,
size=round(path.stat().st_size / 1_048_576, 2),
protected=metadata.protected,
size=round(path.stat().st_size / 1_048_576, 2),
)
async def async_list_backups(self, **kwargs: Any) -> list[UploadedBackup]:
async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]:
"""List backups."""
if not self._loaded_backups:
await self.load_backups()
@@ -111,15 +109,14 @@ class CoreLocalBackupAgent(LocalBackupAgent):
async def async_get_backup(
self,
*,
slug: str,
backup_id: str,
**kwargs: Any,
) -> UploadedBackup | None:
) -> LocalBackup | None:
"""Return a backup."""
if not self._loaded_backups:
await self.load_backups()
if not (backup := self._backups.get(slug)):
if not (backup := self._backups.get(backup_id)):
return None
if not await self._hass.async_add_executor_job(backup.path.exists):
@@ -128,23 +125,23 @@ class CoreLocalBackupAgent(LocalBackupAgent):
"Removing tracked backup (%s) that does not exists on the expected"
" path %s"
),
backup.slug,
backup.backup_id,
backup.path,
)
self._backups.pop(slug)
self._backups.pop(backup_id)
return None
return backup
def get_backup_path(self, slug: str) -> Path:
def get_backup_path(self, backup_id: str) -> Path:
"""Return the local path to a backup."""
return self._backup_dir / f"{slug}.tar"
return self._backup_dir / f"{backup_id}.tar"
async def async_remove_backup(self, *, slug: str, **kwargs: Any) -> None:
async def async_remove_backup(self, backup_id: str, **kwargs: Any) -> None:
"""Remove a backup."""
if (backup := await self.async_get_backup(slug=slug)) is None:
if (backup := await self.async_get_backup(backup_id)) is None:
return
await self._hass.async_add_executor_job(backup.path.unlink, True) # type: ignore[attr-defined]
LOGGER.debug("Removed backup located at %s", backup.path) # type: ignore[attr-defined]
self._backups.pop(slug)
await self._hass.async_add_executor_job(backup.path.unlink, True)
LOGGER.debug("Removed backup located at %s", backup.path)
self._backups.pop(backup_id)

View File

@@ -31,13 +31,13 @@ def async_register_http_views(hass: HomeAssistant) -> None:
class DownloadBackupView(HomeAssistantView):
"""Generate backup view."""
url = "/api/backup/download/{slug}"
url = "/api/backup/download/{backup_id}"
name = "api:backup:download"
async def get(
self,
request: Request,
slug: str,
backup_id: str,
) -> FileResponse | Response:
"""Download a backup file."""
if not request["hass_user"].is_admin:
@@ -51,7 +51,7 @@ class DownloadBackupView(HomeAssistantView):
if agent_id not in manager.backup_agents:
return Response(status=HTTPStatus.BAD_REQUEST)
agent = manager.backup_agents[agent_id]
backup = await agent.async_get_backup(slug=slug)
backup = await agent.async_get_backup(backup_id)
# We don't need to check if the path exists, aiohttp.FileResponse will handle
# that
@@ -60,10 +60,10 @@ class DownloadBackupView(HomeAssistantView):
if agent_id in manager.local_backup_agents:
local_agent = manager.local_backup_agents[agent_id]
path = local_agent.get_backup_path(slug=slug)
path = local_agent.get_backup_path(backup_id)
else:
path = manager.temp_backup_dir / f"{slug}.tar"
await agent.async_download_backup(id=backup.id, path=path)
path = manager.temp_backup_dir / f"{backup_id}.tar"
await agent.async_download_backup(backup_id, path=path)
# TODO: We need a callback to remove the temp file once the download is complete
return FileResponse(

View File

@@ -53,7 +53,7 @@ _BackupT = TypeVar("_BackupT", bound=BaseBackup, default=BaseBackup)
class NewBackup:
"""New backup class."""
slug: str
backup_id: str
@dataclass(slots=True)
@@ -185,7 +185,7 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]):
@abc.abstractmethod
async def async_restore_backup(
self,
slug: str,
backup_id: str,
*,
agent_id: str,
password: str | None,
@@ -218,17 +218,17 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]):
) -> tuple[dict[str, Backup], dict[str, Exception]]:
"""Get backups.
Return a dictionary of Backup instances keyed by their slug.
Return a dictionary of Backup instances keyed by their ID.
"""
@abc.abstractmethod
async def async_get_backup(
self, *, slug: str, **kwargs: Any
self, backup_id: str, **kwargs: Any
) -> tuple[_BackupT | None, dict[str, Exception]]:
"""Get a backup."""
@abc.abstractmethod
async def async_remove_backup(self, *, slug: str, **kwargs: Any) -> None:
async def async_remove_backup(self, backup_id: str, **kwargs: Any) -> None:
"""Remove a backup."""
@abc.abstractmethod
@@ -265,12 +265,12 @@ class BackupManager(BaseBackupManager[Backup]):
self.backup_agents[agent_id].async_upload_backup(
path=path,
metadata=BackupUploadMetadata(
homeassistant=HAVERSION,
size=backup.size,
backup_id=backup.backup_id,
date=backup.date,
slug=backup.slug,
homeassistant=HAVERSION,
name=backup.name,
protected=backup.protected,
size=backup.size,
),
)
for agent_id in agent_ids
@@ -304,21 +304,21 @@ class BackupManager(BaseBackupManager[Backup]):
if isinstance(result, BaseException):
raise result
for agent_backup in result:
if agent_backup.slug not in backups:
backups[agent_backup.slug] = Backup(
slug=agent_backup.slug,
name=agent_backup.name,
date=agent_backup.date,
if agent_backup.backup_id not in backups:
backups[agent_backup.backup_id] = Backup(
agent_ids=[],
size=agent_backup.size,
backup_id=agent_backup.backup_id,
date=agent_backup.date,
name=agent_backup.name,
protected=agent_backup.protected,
size=agent_backup.size,
)
backups[agent_backup.slug].agent_ids.append(agent_ids[idx])
backups[agent_backup.backup_id].agent_ids.append(agent_ids[idx])
return (backups, agent_errors)
async def async_get_backup(
self, *, slug: str, **kwargs: Any
self, backup_id: str, **kwargs: Any
) -> tuple[Backup | None, dict[str, Exception]]:
"""Return a backup."""
backup: Backup | None = None
@@ -327,7 +327,7 @@ class BackupManager(BaseBackupManager[Backup]):
get_backup_results = await asyncio.gather(
*(
agent.async_get_backup(slug=slug)
agent.async_get_backup(backup_id)
for agent in self.backup_agents.values()
),
return_exceptions=True,
@@ -342,23 +342,23 @@ class BackupManager(BaseBackupManager[Backup]):
continue
if backup is None:
backup = Backup(
slug=result.slug,
name=result.name,
date=result.date,
agent_ids=[],
size=result.size,
backup_id=result.backup_id,
date=result.date,
name=result.name,
protected=result.protected,
size=result.size,
)
backup.agent_ids.append(agent_ids[idx])
return (backup, agent_errors)
async def async_remove_backup(self, *, slug: str, **kwargs: Any) -> None:
async def async_remove_backup(self, backup_id: str, **kwargs: Any) -> None:
"""Remove a backup."""
for agent in self.backup_agents.values():
if not hasattr(agent, "async_remove_backup"):
continue
await agent.async_remove_backup(slug=slug)
await agent.async_remove_backup(backup_id)
async def async_receive_backup(
self,
@@ -415,7 +415,7 @@ class BackupManager(BaseBackupManager[Backup]):
if local_file_paths:
tar_file_path = local_file_paths[0]
else:
tar_file_path = self.temp_backup_dir / f"{backup.slug}.tar"
tar_file_path = self.temp_backup_dir / f"{backup.backup_id}.tar"
for local_path in local_file_paths:
shutil.copy(target_temp_file, local_path)
temp_dir_handler.cleanup()
@@ -430,7 +430,7 @@ class BackupManager(BaseBackupManager[Backup]):
return
local_file_paths = [
self.local_backup_agents[agent_id].get_backup_path(backup.slug)
self.local_backup_agents[agent_id].get_backup_path(backup.backup_id)
for agent_id in agent_ids
if agent_id in self.local_backup_agents
]
@@ -464,23 +464,23 @@ class BackupManager(BaseBackupManager[Backup]):
raise HomeAssistantError("Invalid agent selected")
backup_name = name or f"Core {HAVERSION}"
date_str = dt_util.now().isoformat()
slug = _generate_slug(date_str, backup_name)
backup_id = _generate_backup_id(date_str, backup_name)
self.backup_task = self.hass.async_create_task(
self._async_create_backup(
addons_included=addons_included,
agent_ids=agent_ids,
backup_id=backup_id,
backup_name=backup_name,
database_included=database_included,
date_str=date_str,
folders_included=folders_included,
on_progress=on_progress,
password=password,
slug=slug,
),
name="backup_manager_create_backup",
eager_start=False, # To ensure the task is not started before we return
)
return NewBackup(slug=slug)
return NewBackup(backup_id=backup_id)
async def _async_create_backup(
self,
@@ -493,13 +493,13 @@ class BackupManager(BaseBackupManager[Backup]):
folders_included: list[str] | None,
on_progress: Callable[[BackupProgress], None] | None,
password: str | None,
slug: str,
backup_id: str,
) -> BaseBackup:
"""Generate a backup."""
success = False
local_file_paths = [
self.local_backup_agents[agent_id].get_backup_path(slug)
self.local_backup_agents[agent_id].get_backup_path(backup_id)
for agent_id in agent_ids
if agent_id in self.local_backup_agents
]
@@ -508,17 +508,17 @@ class BackupManager(BaseBackupManager[Backup]):
await self.async_pre_backup_actions()
backup_data = {
"slug": slug,
"name": backup_name,
"compressed": True,
"date": date_str,
"type": "partial",
"folders": ["homeassistant"],
"homeassistant": {
"exclude_database": not database_included,
"version": HAVERSION,
},
"compressed": True,
"name": backup_name,
"protected": password is not None,
"slug": backup_id,
"type": "partial",
}
tar_file_path, size_in_bytes = await self.hass.async_add_executor_job(
@@ -529,15 +529,15 @@ class BackupManager(BaseBackupManager[Backup]):
password,
)
backup = BaseBackup(
slug=slug,
name=backup_name,
backup_id=backup_id,
date=date_str,
size=round(size_in_bytes / 1_048_576, 2),
name=backup_name,
protected=password is not None,
size=round(size_in_bytes / 1_048_576, 2),
)
LOGGER.debug(
"Generated new backup with slug %s, uploading to agents %s",
slug,
"Generated new backup with backup_id %s, uploading to agents %s",
backup_id,
agent_ids,
)
await self._async_upload_backup(
@@ -564,7 +564,7 @@ class BackupManager(BaseBackupManager[Backup]):
if tar_file_paths:
tar_file_path = tar_file_paths[0]
else:
tar_file_path = self.temp_backup_dir / f"{backup_data['slug']}.tar"
tar_file_path = self.temp_backup_dir / f"{backup_data['backup_id']}.tar"
if not (backup_dir := tar_file_path.parent).exists():
LOGGER.debug("Creating backup directory %s", backup_dir)
backup_dir.mkdir()
@@ -600,7 +600,7 @@ class BackupManager(BaseBackupManager[Backup]):
async def async_restore_backup(
self,
slug: str,
backup_id: str,
*,
agent_id: str,
password: str | None,
@@ -614,17 +614,21 @@ class BackupManager(BaseBackupManager[Backup]):
if agent_id in self.local_backup_agents:
local_agent = self.local_backup_agents[agent_id]
if not await local_agent.async_get_backup(slug=slug):
raise HomeAssistantError(f"Backup {slug} not found in agent {agent_id}")
path = local_agent.get_backup_path(slug=slug)
if not await local_agent.async_get_backup(backup_id):
raise HomeAssistantError(
f"Backup {backup_id} not found in agent {agent_id}"
)
path = local_agent.get_backup_path(backup_id)
else:
path = self.temp_backup_dir / f"{slug}.tar"
path = self.temp_backup_dir / f"{backup_id}.tar"
agent = self.backup_agents[agent_id]
if not (backup := await agent.async_get_backup(slug=slug)):
raise HomeAssistantError(f"Backup {slug} not found in agent {agent_id}")
await agent.async_download_backup(id=backup.id, path=path)
if not await agent.async_get_backup(backup_id):
raise HomeAssistantError(
f"Backup {backup_id} not found in agent {agent_id}"
)
await agent.async_download_backup(backup_id, path=path)
path = local_agent.get_backup_path(slug)
path = local_agent.get_backup_path(backup_id)
def _write_restore_file() -> None:
"""Write the restore file."""
@@ -637,6 +641,6 @@ class BackupManager(BaseBackupManager[Backup]):
await self.hass.services.async_call("homeassistant", "restart", {})
def _generate_slug(date: str, name: str) -> str:
"""Generate a backup slug."""
def _generate_backup_id(date: str, name: str) -> str:
"""Generate a backup ID."""
return hashlib.sha1(f"{date} - {name}".lower().encode()).hexdigest()[:8]

View File

@@ -7,10 +7,10 @@ from dataclasses import asdict, dataclass
class BaseBackup:
"""Base backup class."""
backup_id: str
date: str
name: str
protected: bool
slug: str
size: float
def as_dict(self) -> dict:
@@ -22,9 +22,9 @@ class BaseBackup:
class BackupUploadMetadata:
"""Backup upload metadata."""
backup_id: str # The ID of the backup
date: str # The date the backup was created
slug: str # The slug of the backup
size: float # The size of the backup (in bytes)
name: str # The name of the backup
homeassistant: str # The version of Home Assistant that created the backup
name: str # The name of the backup
protected: bool # If the backup is protected
size: float # The size of the backup (in bytes)

View File

@@ -20,9 +20,9 @@ def read_backup(backup_path: Path) -> BaseBackup:
raise KeyError("backup.json not found in tar file")
data = json_loads_object(data_file.read())
return BaseBackup(
slug=cast(str, data["slug"]),
name=cast(str, data["name"]),
backup_id=cast(str, data["slug"]),
date=cast(str, data["date"]),
size=round(backup_path.stat().st_size / 1_048_576, 2),
name=cast(str, data["name"]),
protected=cast(bool, data.get("protected", False)),
size=round(backup_path.stat().st_size / 1_048_576, 2),
)

View File

@@ -60,7 +60,7 @@ async def handle_info(
@websocket_api.websocket_command(
{
vol.Required("type"): "backup/details",
vol.Required("slug"): str,
vol.Required("backup_id"): str,
}
)
@websocket_api.async_response
@@ -69,9 +69,9 @@ async def handle_details(
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Get backup details for a specific slug."""
"""Get backup details for a specific backup."""
backup, agent_errors = await hass.data[DATA_MANAGER].async_get_backup(
slug=msg["slug"]
msg["backup_id"]
)
connection.send_result(
msg["id"],
@@ -88,7 +88,7 @@ async def handle_details(
@websocket_api.websocket_command(
{
vol.Required("type"): "backup/remove",
vol.Required("slug"): str,
vol.Required("backup_id"): str,
}
)
@websocket_api.async_response
@@ -98,7 +98,7 @@ async def handle_remove(
msg: dict[str, Any],
) -> None:
"""Remove a backup."""
await hass.data[DATA_MANAGER].async_remove_backup(slug=msg["slug"])
await hass.data[DATA_MANAGER].async_remove_backup(msg["backup_id"])
connection.send_result(msg["id"])
@@ -106,7 +106,7 @@ async def handle_remove(
@websocket_api.websocket_command(
{
vol.Required("type"): "backup/restore",
vol.Required("slug"): str,
vol.Required("backup_id"): str,
vol.Required("agent_id"): str,
vol.Optional("password"): str,
}
@@ -119,7 +119,7 @@ async def handle_restore(
) -> None:
"""Restore a backup."""
await hass.data[DATA_MANAGER].async_restore_backup(
slug=msg["slug"],
msg["backup_id"],
agent_id=msg["agent_id"],
password=msg.get("password"),
)
@@ -245,7 +245,6 @@ async def backup_agents_list_backups(
vol.Required("type"): "backup/agents/download",
vol.Required("agent_id"): str,
vol.Required("backup_id"): str,
vol.Required("slug"): str,
}
)
@websocket_api.async_response
@@ -262,9 +261,9 @@ async def backup_agents_download(
)
return
try:
path = manager.temp_backup_dir / f"{msg["slug"]}.tar"
path = manager.temp_backup_dir / f"{msg["backup_id"]}.tar"
await agent.async_download_backup(
id=msg["backup_id"],
msg["backup_id"],
path=path,
)
except Exception as err: # noqa: BLE001

View File

@@ -5,12 +5,11 @@ from __future__ import annotations
import logging
from pathlib import Path
from typing import Any
from uuid import uuid4
from homeassistant.components.backup import (
BackupAgent,
BackupUploadMetadata,
UploadedBackup,
BaseBackup,
)
from homeassistant.core import HomeAssistant
@@ -32,25 +31,23 @@ class KitchenSinkBackupAgent(BackupAgent):
super().__init__()
self.name = name
self._uploads = [
UploadedBackup(
id="def456",
BaseBackup(
backup_id="abc123",
date="1970-01-01T00:00:00Z",
name="Kitchen sink syncer",
protected=False,
slug="abc123",
size=1234,
date="1970-01-01T00:00:00Z",
)
]
async def async_download_backup(
self,
*,
id: str,
backup_id: str,
path: Path,
**kwargs: Any,
) -> None:
"""Download a backup file."""
LOGGER.info("Downloading backup %s to %s", id, path)
LOGGER.info("Downloading backup %s to %s", backup_id, path)
async def async_upload_backup(
self,
@@ -62,28 +59,26 @@ class KitchenSinkBackupAgent(BackupAgent):
"""Upload a backup."""
LOGGER.info("Uploading backup %s %s", path.name, metadata)
self._uploads.append(
UploadedBackup(
id=uuid4().hex,
BaseBackup(
backup_id=metadata.backup_id,
date=metadata.date,
name=metadata.name,
protected=metadata.protected,
slug=metadata.slug,
size=metadata.size,
date=metadata.date,
)
)
async def async_list_backups(self, **kwargs: Any) -> list[UploadedBackup]:
async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]:
"""List synced backups."""
return self._uploads
async def async_get_backup(
self,
*,
slug: str,
backup_id: str,
**kwargs: Any,
) -> UploadedBackup | None:
) -> BaseBackup | None:
"""Return a backup."""
for backup in self._uploads:
if backup.slug == slug:
if backup.backup_id == backup_id:
return backup
return None

View File

@@ -10,12 +10,11 @@ from homeassistant.components.backup import (
DOMAIN,
BackupAgent,
BackupUploadMetadata,
UploadedBackup,
BaseBackup,
)
from homeassistant.components.backup.backup import LocalBackup
from homeassistant.components.backup.const import DATA_MANAGER
from homeassistant.components.backup.manager import Backup
from homeassistant.components.backup.models import BaseBackup
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
@@ -23,29 +22,28 @@ from homeassistant.setup import async_setup_component
LOCAL_AGENT_ID = f"{DOMAIN}.local"
TEST_BASE_BACKUP = BaseBackup(
slug="abc123",
name="Test",
backup_id="abc123",
date="1970-01-01T00:00:00.000Z",
size=0.0,
name="Test",
protected=False,
size=0.0,
)
TEST_BACKUP = Backup(
agent_ids=["backup.local"],
slug="abc123",
name="Test",
backup_id="abc123",
date="1970-01-01T00:00:00.000Z",
size=0.0,
name="Test",
protected=False,
size=0.0,
)
TEST_BACKUP_PATH = Path("abc123.tar")
TEST_LOCAL_BACKUP = LocalBackup(
id="abc123",
slug="abc123",
name="Test",
date="1970-01-01T00:00:00.000Z",
backup_id="abc123",
name="Test",
path=Path("abc123.tar"),
size=0.0,
protected=False,
size=0.0,
)
@@ -74,35 +72,33 @@ class BackupAgentTest(BackupAgent):
) -> None:
"""Upload a backup."""
async def async_list_backups(self, **kwargs: Any) -> list[UploadedBackup]:
async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]:
"""List backups."""
return [
UploadedBackup(
id="abc123",
BaseBackup(
backup_id="abc123",
date="1970-01-01T00:00:00Z",
name="Test",
protected=False,
size=13.37,
slug="abc123",
)
]
async def async_get_backup(
self,
*,
slug: str,
backup_id: str,
**kwargs: Any,
) -> UploadedBackup | None:
) -> BaseBackup | None:
"""Return a backup."""
if slug != "abc123":
if backup_id != "abc123":
return None
return UploadedBackup(
id="abc123",
return BaseBackup(
backup_id="abc123",
date="1970-01-01T00:00:00Z",
name="Test",
protected=False,
size=13.37,
slug="abc123",
)
@@ -119,7 +115,7 @@ async def setup_backup_integration(
return result
local_agent = hass.data[DATA_MANAGER].backup_agents[LOCAL_AGENT_ID]
local_agent._backups = {backups.slug: backups for backups in backups}
local_agent._backups = {backup.backup_id: backup for backup in backups}
local_agent._loaded_backups = True
return result

View File

@@ -79,12 +79,11 @@
'result': list([
dict({
'agent_id': 'domain.test',
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00Z',
'id': 'abc123',
'name': 'Test',
'protected': False,
'size': 13.37,
'slug': 'abc123',
}),
]),
'success': True,
@@ -97,12 +96,11 @@
'result': list([
dict({
'agent_id': 'domain.test',
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00Z',
'id': 'abc123',
'name': 'Test',
'protected': False,
'size': 13.37,
'slug': 'abc123',
}),
]),
'success': True,
@@ -367,11 +365,11 @@
'agent_ids': list([
'backup.local',
]),
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00.000Z',
'name': 'Test',
'protected': False,
'size': 0.0,
'slug': 'abc123',
}),
}),
'success': True,
@@ -401,11 +399,11 @@
'agent_ids': list([
'backup.local',
]),
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00.000Z',
'name': 'Test',
'protected': False,
'size': 0.0,
'slug': 'abc123',
}),
}),
'success': True,
@@ -460,7 +458,7 @@
dict({
'id': 1,
'result': dict({
'slug': '27f5c632',
'backup_id': '27f5c632',
}),
'success': True,
'type': 'result',
@@ -481,7 +479,7 @@
dict({
'id': 1,
'result': dict({
'slug': '27f5c632',
'backup_id': '27f5c632',
}),
'success': True,
'type': 'result',
@@ -502,7 +500,7 @@
dict({
'id': 1,
'result': dict({
'slug': '27f5c632',
'backup_id': '27f5c632',
}),
'success': True,
'type': 'result',
@@ -523,7 +521,7 @@
dict({
'id': 1,
'result': dict({
'slug': 'abc123',
'backup_id': 'abc123',
}),
'success': True,
'type': 'result',
@@ -533,7 +531,7 @@
dict({
'id': 1,
'result': dict({
'slug': 'abc123',
'backup_id': 'abc123',
}),
'success': True,
'type': 'result',
@@ -562,11 +560,11 @@
'agent_ids': list([
'backup.local',
]),
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00.000Z',
'name': 'Test',
'protected': False,
'size': 0.0,
'slug': 'abc123',
}),
]),
}),
@@ -587,11 +585,11 @@
'agent_ids': list([
'backup.local',
]),
'backup_id': 'abc123',
'date': '1970-01-01T00:00:00.000Z',
'name': 'Test',
'protected': False,
'size': 0.0,
'slug': 'abc123',
}),
]),
}),

View File

@@ -133,9 +133,9 @@ async def test_load_backups(hass: HomeAssistant) -> None:
patch(
"homeassistant.components.backup.util.json_loads_object",
return_value={
"slug": TEST_LOCAL_BACKUP.slug,
"name": TEST_LOCAL_BACKUP.name,
"date": TEST_LOCAL_BACKUP.date,
"name": TEST_LOCAL_BACKUP.name,
"slug": TEST_LOCAL_BACKUP.backup_id,
},
),
patch(
@@ -145,7 +145,7 @@ async def test_load_backups(hass: HomeAssistant) -> None:
):
await manager.backup_agents[LOCAL_AGENT_ID].load_backups()
backups, agent_errors = await manager.async_get_backups()
assert backups == {TEST_BACKUP.slug: TEST_BACKUP}
assert backups == {TEST_BACKUP.backup_id: TEST_BACKUP}
assert agent_errors == {}
@@ -181,11 +181,11 @@ async def test_removing_backup(
await manager.load_platforms()
local_agent = manager.backup_agents[LOCAL_AGENT_ID]
local_agent._backups = {TEST_LOCAL_BACKUP.slug: TEST_LOCAL_BACKUP}
local_agent._backups = {TEST_LOCAL_BACKUP.backup_id: TEST_LOCAL_BACKUP}
local_agent._loaded_backups = True
with patch("pathlib.Path.exists", return_value=True):
await manager.async_remove_backup(slug=TEST_LOCAL_BACKUP.slug)
await manager.async_remove_backup(TEST_LOCAL_BACKUP.backup_id)
assert "Removed backup located at" in caplog.text
@@ -199,7 +199,7 @@ async def test_removing_non_existing_backup(
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
await manager.load_platforms()
await manager.async_remove_backup(slug="non_existing")
await manager.async_remove_backup("non_existing")
assert "Removed backup located at" not in caplog.text
@@ -214,18 +214,18 @@ async def test_getting_backup_that_does_not_exist(
await manager.load_platforms()
local_agent = manager.backup_agents[LOCAL_AGENT_ID]
local_agent._backups = {TEST_LOCAL_BACKUP.slug: TEST_LOCAL_BACKUP}
local_agent._backups = {TEST_LOCAL_BACKUP.backup_id: TEST_LOCAL_BACKUP}
local_agent._loaded_backups = True
with patch("pathlib.Path.exists", return_value=False):
backup, agent_errors = await manager.async_get_backup(
slug=TEST_LOCAL_BACKUP.slug
TEST_LOCAL_BACKUP.backup_id
)
assert backup is None
assert agent_errors == {}
assert (
f"Removing tracked backup ({TEST_LOCAL_BACKUP.slug}) that "
f"Removing tracked backup ({TEST_LOCAL_BACKUP.backup_id}) that "
f"does not exists on the expected path {TEST_LOCAL_BACKUP.path}"
) in caplog.text
@@ -278,7 +278,7 @@ async def test_async_create_backup(
hass, manager, mocked_json_bytes, mocked_tarfile, **params
)
assert "Generated new backup with slug " in caplog.text
assert "Generated new backup with backup_id " in caplog.text
assert "Creating backup directory" in caplog.text
assert "Loaded 0 platforms" in caplog.text
assert "Loaded 1 agents" in caplog.text
@@ -457,7 +457,7 @@ async def test_async_trigger_restore(
await manager.load_platforms()
local_agent = manager.backup_agents[LOCAL_AGENT_ID]
local_agent._backups = {TEST_LOCAL_BACKUP.slug: TEST_LOCAL_BACKUP}
local_agent._backups = {TEST_LOCAL_BACKUP.backup_id: TEST_LOCAL_BACKUP}
local_agent._loaded_backups = True
with (
@@ -466,7 +466,7 @@ async def test_async_trigger_restore(
patch("homeassistant.core.ServiceRegistry.async_call") as mocked_service_call,
):
await manager.async_restore_backup(
TEST_LOCAL_BACKUP.slug, agent_id=LOCAL_AGENT_ID, password=None
TEST_LOCAL_BACKUP.backup_id, agent_id=LOCAL_AGENT_ID, password=None
)
assert (
mocked_write_text.call_args[0][0]
@@ -486,7 +486,7 @@ async def test_async_trigger_restore_with_password(
await manager.load_platforms()
local_agent = manager.backup_agents[LOCAL_AGENT_ID]
local_agent._backups = {TEST_LOCAL_BACKUP.slug: TEST_LOCAL_BACKUP}
local_agent._backups = {TEST_LOCAL_BACKUP.backup_id: TEST_LOCAL_BACKUP}
local_agent._loaded_backups = True
with (
@@ -495,7 +495,7 @@ async def test_async_trigger_restore_with_password(
patch("homeassistant.core.ServiceRegistry.async_call") as mocked_service_call,
):
await manager.async_restore_backup(
slug=TEST_LOCAL_BACKUP.slug, agent_id=LOCAL_AGENT_ID, password="abc123"
TEST_LOCAL_BACKUP.backup_id, agent_id=LOCAL_AGENT_ID, password="abc123"
)
assert (
mocked_write_text.call_args[0][0]
@@ -516,5 +516,5 @@ async def test_async_trigger_restore_missing_backup(hass: HomeAssistant) -> None
with pytest.raises(HomeAssistantError, match="Backup abc123 not found"):
await manager.async_restore_backup(
TEST_LOCAL_BACKUP.slug, agent_id=LOCAL_AGENT_ID, password=None
TEST_LOCAL_BACKUP.backup_id, agent_id=LOCAL_AGENT_ID, password=None
)

View File

@@ -108,7 +108,9 @@ async def test_details(
await hass.async_block_till_done()
with patch("pathlib.Path.exists", return_value=True):
await client.send_json_auto_id({"type": "backup/details", "slug": "abc123"})
await client.send_json_auto_id(
{"type": "backup/details", "backup_id": "abc123"}
)
assert await client.receive_json() == snapshot
@@ -132,7 +134,9 @@ async def test_details_with_errors(
patch("pathlib.Path.exists", return_value=True),
patch.object(BackupAgentTest, "async_get_backup", side_effect=side_effect),
):
await client.send_json_auto_id({"type": "backup/details", "slug": "abc123"})
await client.send_json_auto_id(
{"type": "backup/details", "backup_id": "abc123"}
)
assert await client.receive_json() == snapshot
@@ -158,7 +162,7 @@ async def test_remove(
with patch(
"homeassistant.components.backup.manager.BackupManager.async_remove_backup",
):
await client.send_json_auto_id({"type": "backup/remove", "slug": "abc123"})
await client.send_json_auto_id({"type": "backup/remove", "backup_id": "abc123"})
assert await client.receive_json() == snapshot
@@ -281,7 +285,11 @@ async def test_restore(
"homeassistant.components.backup.manager.BackupManager.async_restore_backup",
):
await client.send_json_auto_id(
{"type": "backup/restore", "slug": "abc123", "agent_id": "backup.local"}
{
"type": "backup/restore",
"backup_id": "abc123",
"agent_id": "backup.local",
}
)
assert await client.receive_json() == snapshot
@@ -481,15 +489,14 @@ async def test_agents_download(
await client.send_json_auto_id(
{
"type": "backup/agents/download",
"slug": "abc123",
"agent_id": "domain.test",
"backup_id": "abc123",
}
)
with patch.object(BackupAgentTest, "async_download_backup") as download_mock:
assert await client.receive_json() == snapshot
assert download_mock.call_args[0] == ("abc123",)
assert download_mock.call_args[1] == {
"id": "abc123",
"path": Path(hass.config.path("tmp_backups"), "abc123.tar"),
}
@@ -509,7 +516,6 @@ async def test_agents_download_exception(
await client.send_json_auto_id(
{
"type": "backup/agents/download",
"slug": "abc123",
"agent_id": "domain.test",
"backup_id": "abc123",
}
@@ -533,7 +539,6 @@ async def test_agents_download_unknown_agent(
await client.send_json_auto_id(
{
"type": "backup/agents/download",
"slug": "abc123",
"agent_id": "domain.test",
"backup_id": "abc123",
}

View File

@@ -3,7 +3,6 @@
from collections.abc import AsyncGenerator
from io import StringIO
from unittest.mock import patch
from uuid import UUID
import pytest
@@ -69,12 +68,11 @@ async def test_agents_list_backups(
assert response["result"] == [
{
"agent_id": "kitchen_sink.syncer",
"backup_id": "abc123",
"date": "1970-01-01T00:00:00Z",
"id": "def456",
"slug": "abc123",
"size": 1234,
"name": "Kitchen sink syncer",
"protected": False,
"size": 1234,
}
]
@@ -86,13 +84,11 @@ async def test_agents_download(
) -> None:
"""Test backup agents download."""
client = await hass_ws_client(hass)
backup_id = "def456"
slug = "abc123"
backup_id = "abc123"
await client.send_json_auto_id(
{
"type": "backup/agents/download",
"slug": slug,
"agent_id": "kitchen_sink.syncer",
"backup_id": backup_id,
}
@@ -100,7 +96,7 @@ async def test_agents_download(
response = await client.receive_json()
assert response["success"]
path = hass.config.path(f"tmp_backups/{slug}.tar")
path = hass.config.path(f"tmp_backups/{backup_id}.tar")
assert f"Downloading backup {backup_id} to {path}" in caplog.text
@@ -114,18 +110,16 @@ async def test_agents_upload(
"""Test backup agents upload."""
ws_client = await hass_ws_client(hass, hass_supervisor_access_token)
client = await hass_client()
slug = "test-backup"
backup_id = "test-backup"
test_backup = BaseBackup(
slug=slug,
name="Test",
backup_id=backup_id,
date="1970-01-01T00:00:00.000Z",
size=0.0,
name="Test",
protected=False,
size=0.0,
)
uuid = UUID(int=123456)
with (
patch("homeassistant.components.kitchen_sink.backup.uuid4", return_value=uuid),
patch(
"homeassistant.components.backup.manager.BackupManager.async_get_backup",
) as fetch_backup,
@@ -141,22 +135,20 @@ async def test_agents_upload(
)
assert resp.status == 201
backup_name = f"{slug}.tar"
backup_name = f"{backup_id}.tar"
assert f"Uploading backup {backup_name}" in caplog.text
with patch("homeassistant.components.kitchen_sink.backup.uuid4", return_value=uuid):
await ws_client.send_json_auto_id({"type": "backup/agents/list_backups"})
response = await ws_client.receive_json()
await ws_client.send_json_auto_id({"type": "backup/agents/list_backups"})
response = await ws_client.receive_json()
assert response["success"]
backup_list = response["result"]
assert len(backup_list) == 2
assert backup_list[1] == {
"agent_id": "kitchen_sink.syncer",
"backup_id": backup_id,
"date": test_backup.date,
"id": uuid.hex,
"slug": slug,
"size": 0.0,
"name": test_backup.name,
"protected": test_backup.protected,
"size": 0.0,
}