From d44e995aed60fc39c789529f70e79f17380a60a4 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Sat, 7 Dec 2024 04:27:23 -0500 Subject: [PATCH] Add size in bytes to backups (#5473) --- supervisor/api/backups.py | 3 +++ supervisor/api/const.py | 1 + supervisor/backups/backup.py | 10 ++++++++-- tests/api/test_backups.py | 38 ++++++++++++++++++++++++++++++++++-- tests/conftest.py | 7 +++++-- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/supervisor/api/backups.py b/supervisor/api/backups.py index e0c5014e797..169819f3364 100644 --- a/supervisor/api/backups.py +++ b/supervisor/api/backups.py @@ -54,6 +54,7 @@ ATTR_ADDITIONAL_LOCATIONS, ATTR_BACKGROUND, ATTR_LOCATIONS, + ATTR_SIZE_BYTES, CONTENT_TYPE_TAR, ) from .utils import api_process, api_validate @@ -145,6 +146,7 @@ def _list_backups(self): ATTR_DATE: backup.date, ATTR_TYPE: backup.sys_type, ATTR_SIZE: backup.size, + ATTR_SIZE_BYTES: backup.size_bytes, ATTR_LOCATION: backup.location, ATTR_LOCATIONS: backup.locations, ATTR_PROTECTED: backup.protected, @@ -216,6 +218,7 @@ async def backup_info(self, request): ATTR_NAME: backup.name, ATTR_DATE: backup.date, ATTR_SIZE: backup.size, + ATTR_SIZE_BYTES: backup.size_bytes, ATTR_COMPRESSED: backup.compressed, ATTR_PROTECTED: backup.protected, ATTR_SUPERVISOR_VERSION: backup.supervisor_version, diff --git a/supervisor/api/const.py b/supervisor/api/const.py index 402fb1af8d4..fb0f7287d1d 100644 --- a/supervisor/api/const.py +++ b/supervisor/api/const.py @@ -59,6 +59,7 @@ ATTR_SAFE_MODE = "safe_mode" ATTR_SEAT = "seat" ATTR_SIGNED = "signed" +ATTR_SIZE_BYTES = "size_bytes" ATTR_STARTUP_TIME = "startup_time" ATTR_STATUS = "status" ATTR_SUBSYSTEM = "subsystem" diff --git a/supervisor/backups/backup.py b/supervisor/backups/backup.py index 8dbd5aa351b..af6406ab45a 100644 --- a/supervisor/backups/backup.py +++ b/supervisor/backups/backup.py @@ -6,6 +6,7 @@ from collections.abc import Awaitable from copy import deepcopy from datetime import timedelta +from functools import cached_property import io import json import logging @@ -213,12 +214,17 @@ def locations(self) -> list[str | None]: key=location_sort_key, ) - @property + @cached_property def size(self) -> float: """Return backup size.""" + return round(self.size_bytes / 1048576, 2) # calc mbyte + + @cached_property + def size_bytes(self) -> int: + """Return backup size in bytes.""" if not self.tarfile.is_file(): return 0 - return round(self.tarfile.stat().st_size / 1048576, 2) # calc mbyte + return self.tarfile.stat().st_size @property def is_new(self) -> bool: diff --git a/tests/api/test_backups.py b/tests/api/test_backups.py index 3a84014254e..f409cdbe601 100644 --- a/tests/api/test_backups.py +++ b/tests/api/test_backups.py @@ -25,8 +25,12 @@ from tests.const import TEST_ADDON_SLUG -async def test_info(api_client, coresys: CoreSys, mock_full_backup: Backup): +async def test_info( + api_client, coresys: CoreSys, mock_full_backup: Backup, tmp_path: Path +): """Test info endpoint.""" + copy(get_fixture_path("backup_example.tar"), tmp_path / "test_backup.tar") + resp = await api_client.get("/backups/info") result = await resp.json() assert result["data"]["days_until_stale"] == 30 @@ -35,10 +39,38 @@ async def test_info(api_client, coresys: CoreSys, mock_full_backup: Backup): assert result["data"]["backups"][0]["content"]["homeassistant"] is True assert len(result["data"]["backups"][0]["content"]["addons"]) == 1 assert result["data"]["backups"][0]["content"]["addons"][0] == "local_ssh" + assert result["data"]["backups"][0]["size"] == 0.01 + assert result["data"]["backups"][0]["size_bytes"] == 10240 + +async def test_backup_more_info( + api_client, coresys: CoreSys, mock_full_backup: Backup, tmp_path: Path +): + """Test info endpoint.""" + copy(get_fixture_path("backup_example.tar"), tmp_path / "test_backup.tar") -async def test_list(api_client, coresys: CoreSys, mock_full_backup: Backup): + resp = await api_client.get("/backups/test/info") + result = await resp.json() + assert result["data"]["slug"] == "test" + assert result["data"]["homeassistant"] == "2022.8.0" + assert len(result["data"]["addons"]) == 1 + assert result["data"]["addons"][0] == { + "name": "SSH", + "size": 0, + "slug": "local_ssh", + "version": "1.0.0", + } + assert result["data"]["size"] == 0.01 + assert result["data"]["size_bytes"] == 10240 + assert result["data"]["homeassistant_exclude_database"] is False + + +async def test_list( + api_client, coresys: CoreSys, mock_full_backup: Backup, tmp_path: Path +): """Test list endpoint.""" + copy(get_fixture_path("backup_example.tar"), tmp_path / "test_backup.tar") + resp = await api_client.get("/backups") result = await resp.json() assert len(result["data"]["backups"]) == 1 @@ -46,6 +78,8 @@ async def test_list(api_client, coresys: CoreSys, mock_full_backup: Backup): assert result["data"]["backups"][0]["content"]["homeassistant"] is True assert len(result["data"]["backups"][0]["content"]["addons"]) == 1 assert result["data"]["backups"][0]["content"]["addons"][0] == "local_ssh" + assert result["data"]["backups"][0]["size"] == 0.01 + assert result["data"]["backups"][0]["size_bytes"] == 10240 async def test_options(api_client, coresys: CoreSys): diff --git a/tests/conftest.py b/tests/conftest.py index 055b9fe7dc4..23fd12b6778 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,6 +30,7 @@ ATTR_ADDONS, ATTR_ADDONS_CUSTOM_LIST, ATTR_DATE, + ATTR_EXCLUDE_DATABASE, ATTR_FOLDERS, ATTR_HOMEASSISTANT, ATTR_NAME, @@ -580,7 +581,7 @@ def install_addon_example(coresys: CoreSys, repository): @pytest.fixture async def mock_full_backup(coresys: CoreSys, tmp_path) -> Backup: """Mock a full backup.""" - mock_backup = Backup(coresys, Path(tmp_path, "test_backup"), "test", None) + mock_backup = Backup(coresys, Path(tmp_path, "test_backup.tar"), "test", None) mock_backup.new("Test", utcnow().isoformat(), BackupType.FULL) mock_backup.repositories = ["https://github.com/awesome-developer/awesome-repo"] mock_backup.docker = {} @@ -596,6 +597,7 @@ async def mock_full_backup(coresys: CoreSys, tmp_path) -> Backup: mock_backup._data[ATTR_HOMEASSISTANT] = { ATTR_VERSION: AwesomeVersion("2022.8.0"), ATTR_SIZE: 0, + ATTR_EXCLUDE_DATABASE: False, } coresys.backups._backups = {"test": mock_backup} yield mock_backup @@ -604,7 +606,7 @@ async def mock_full_backup(coresys: CoreSys, tmp_path) -> Backup: @pytest.fixture async def mock_partial_backup(coresys: CoreSys, tmp_path) -> Backup: """Mock a partial backup.""" - mock_backup = Backup(coresys, Path(tmp_path, "test_backup"), "test", None) + mock_backup = Backup(coresys, Path(tmp_path, "test_backup.tar"), "test", None) mock_backup.new("Test", utcnow().isoformat(), BackupType.PARTIAL) mock_backup.repositories = ["https://github.com/awesome-developer/awesome-repo"] mock_backup.docker = {} @@ -620,6 +622,7 @@ async def mock_partial_backup(coresys: CoreSys, tmp_path) -> Backup: mock_backup._data[ATTR_HOMEASSISTANT] = { ATTR_VERSION: AwesomeVersion("2022.8.0"), ATTR_SIZE: 0, + ATTR_EXCLUDE_DATABASE: False, } coresys.backups._backups = {"test": mock_backup} yield mock_backup