diff --git a/src/butter_backup/backup_backends.py b/src/butter_backup/backup_backends.py index a4151d7..b91055a 100644 --- a/src/butter_backup/backup_backends.py +++ b/src/butter_backup/backup_backends.py @@ -38,7 +38,7 @@ class BtrFSRsyncBackend(BackupBackend): config: cp.BtrFSRsyncConfig def do_backup(self, mount_dir: Path) -> None: - logger.info(f"Beginne mit BtrFS-Backup für Speichermedium {self.config.UUID}.") + logger.info(f"Beginne mit BtrFS-Backup für Speichermedium {self.config.Name}.") backup_repository = mount_dir / self.config.BackupRepositoryFolder src_snapshot = self.get_source_snapshot(backup_repository) logger.info(f"Basis-Sicherungskopie: {src_snapshot}.") @@ -124,7 +124,7 @@ class ResticBackend(BackupBackend): config: cp.ResticConfig def do_backup(self, mount_dir: Path) -> None: - logger.info(f"Beginne mit Restic-Backup für Speichermedium {self.config.UUID}.") + logger.info(f"Beginne mit Restic-Backup für Speichermedium {self.config.Name}.") backup_repository = mount_dir / self.config.BackupRepositoryFolder self.copy_files(backup_repository) self.adapt_ownership(backup_repository) diff --git a/src/butter_backup/cli.py b/src/butter_backup/cli.py index 6b4a446..a6a7fe0 100644 --- a/src/butter_backup/cli.py +++ b/src/butter_backup/cli.py @@ -109,7 +109,7 @@ def open( # noqa: A001 if _skip_device( cfg, log_opened=lambda cfg: logger.warning( - f"Speichermedium {cfg.UUID} ist bereits geöffnet. Es wird übersprungen." + f"Speichermedium {cfg.Name} ist bereits geöffnet. Es wird übersprungen." ), ): continue @@ -118,7 +118,7 @@ def open( # noqa: A001 sdm.mount_btrfs_device( decrypted, mount_dir=mount_dir, compression=cfg.Compression ) - typer.echo(f"Speichermedium {cfg.UUID} wurde in {mount_dir} geöffnet.") + typer.echo(f"Speichermedium {cfg.Name} wurde in {mount_dir} geöffnet.") @app.command() @@ -174,10 +174,10 @@ def backup(config: Path = CONFIG_OPTION, verbose: int = VERBOSITY_OPTION) -> Non if _skip_device( cfg, log_missing=lambda cfg: logger.info( - f"Speichermedium {cfg.UUID} existiert nicht. Es wird kein Backup angelegt." + f"Speichermedium {cfg.Name} existiert nicht. Es wird kein Backup angelegt." ), log_opened=lambda cfg: logger.warning( - f"Speichermedium {cfg.UUID} ist bereits geöffnet. Es wird übersprungen." + f"Speichermedium {cfg.Name} ist bereits geöffnet. Es wird übersprungen." ), ): continue diff --git a/src/butter_backup/config_parser.py b/src/butter_backup/config_parser.py index e4e56ab..8884ef8 100644 --- a/src/butter_backup/config_parser.py +++ b/src/butter_backup/config_parser.py @@ -3,7 +3,7 @@ import uuid from collections import Counter from pathlib import Path -from typing import ClassVar +from typing import Any, ClassVar from pydantic import ( BaseModel, @@ -37,6 +37,16 @@ class BaseConfig(BaseModel): ExcludePatternsFile: FilePath | None = None UUID: uuid.UUID Compression: ValidCompressions | None = None + Name: str + + @model_validator(mode="before") + @classmethod + def set_default_name(cls, data: Any) -> Any: + if isinstance(data, dict) and not data.get("name"): + uuid_val = data.get("UUID") + if uuid_val is not None: + return data | {"name": str(uuid_val)} + return data @field_validator("ExcludePatternsFile", mode="before") def expand_tilde_in_exclude_patterns_file_name( diff --git a/src/butter_backup/device_managers.py b/src/butter_backup/device_managers.py index a3bd413..f8a5ffb 100644 --- a/src/butter_backup/device_managers.py +++ b/src/butter_backup/device_managers.py @@ -41,6 +41,7 @@ def prepare_device_for_butterbackend(device: Path) -> cp.BtrFSRsyncConfig: Files=set(), FilesDest="Einzeldateien", Folders={}, + Name="BTRFS-RSYNC-Backup", UUID=volume_uuid, ) return config @@ -74,6 +75,7 @@ def prepare_device_for_resticbackend(device: Path) -> cp.ResticConfig: Compression=compression, DevicePassCmd=device_passcmd, FilesAndFolders=set(), + Name="Restic-Backup", RepositoryPassCmd=repository_passcmd, UUID=volume_uuid, ) diff --git a/tests/config_parser/test_btrfs_config.py b/tests/config_parser/test_btrfs_config.py index 3e7c497..33ef397 100644 --- a/tests/config_parser/test_btrfs_config.py +++ b/tests/config_parser/test_btrfs_config.py @@ -105,6 +105,20 @@ def test_btrfs_config_rejects_duplicate_dest(base_config, folder_dest: str): cp.BtrFSRsyncConfig.model_validate(base_config) +@given(base_config=valid_unparsed_empty_btrfs_config()) +def test_btrfs_config_name_defaults_to_uuid(base_config) -> None: + base_config.pop("name", None) + cfg = cp.BtrFSRsyncConfig.model_validate(base_config) + assert cfg.Name == base_config["UUID"] + + +@given(base_config=valid_unparsed_empty_btrfs_config(), custom_name=st.text(min_size=1)) +def test_btrfs_config_accepts_custom_name(base_config, custom_name: str) -> None: + base_config["name"] = custom_name + cfg = cp.BtrFSRsyncConfig.model_validate(base_config) + assert cfg.Name == custom_name + + @given(base_config=valid_unparsed_empty_btrfs_config()) def test_btrfs_config_uuid_is_mapname(base_config) -> None: cfg = cp.BtrFSRsyncConfig.model_validate(base_config) diff --git a/tests/config_parser/test_parse_configuration.py b/tests/config_parser/test_parse_configuration.py index 8a0d3f0..ded7652 100644 --- a/tests/config_parser/test_parse_configuration.py +++ b/tests/config_parser/test_parse_configuration.py @@ -37,12 +37,14 @@ def test_parse_configuration_warns_on_non_dict_item() -> None: @given( backup_dest_dirs=st.lists(st.text(), min_size=2, max_size=2, unique=True), backup_repository_folder=st.text(), + name=st.text(), pass_cmd=st.text(), uuid=st.uuids(), ) def test_parse_configuration_parses_btrfs_config( backup_dest_dirs: list[str], backup_repository_folder: str, + name: str, pass_cmd: str, uuid: UUID, ) -> None: @@ -53,6 +55,7 @@ def test_parse_configuration_parses_btrfs_config( Files=set(), FilesDest=backup_dest_dirs[1], Folders={Path(source): backup_dest_dirs[0]}, + Name=name, UUID=uuid, ) cfg_lst = cp.parse_configuration(f"[{btrfs_cfg.model_dump_json()}]") @@ -63,13 +66,15 @@ def test_parse_configuration_parses_btrfs_config( backup_dest_dirs=st.lists(st.text(), min_size=2, max_size=2, unique=True), backup_repository_folder=st.text(), device_pass_cmd=st.text(), + name=st.text(), repository_pass_cmd=st.text(), uuid=st.uuids(), ) -def test_load_configuration_parses_restic_config( +def test_load_configuration_parses_restic_config( # noqa: PLR0913 backup_dest_dirs: list[str], backup_repository_folder: str, device_pass_cmd: str, + name: str, repository_pass_cmd: str, uuid: UUID, ) -> None: @@ -78,6 +83,7 @@ def test_load_configuration_parses_restic_config( BackupRepositoryFolder=backup_repository_folder, DevicePassCmd=device_pass_cmd, FilesAndFolders={Path(source)}, + Name=name, RepositoryPassCmd=repository_pass_cmd, UUID=uuid, ) diff --git a/tests/config_parser/test_restic_config.py b/tests/config_parser/test_restic_config.py index bdebedd..a0761d6 100644 --- a/tests/config_parser/test_restic_config.py +++ b/tests/config_parser/test_restic_config.py @@ -54,6 +54,22 @@ def test_restic_config_expands_user(base_config): assert cfg.ExcludePatternsFile == Path(exclude_file.name).expanduser() +@given(base_config=valid_unparsed_empty_restic_config()) +def test_restic_config_name_defaults_to_uuid(base_config) -> None: + base_config.pop("name", None) + cfg = cp.ResticConfig.model_validate(base_config) + assert cfg.Name == base_config["UUID"] + + +@given( + base_config=valid_unparsed_empty_restic_config(), custom_name=st.text(min_size=1) +) +def test_restic_config_accepts_custom_name(base_config, custom_name: str) -> None: + base_config["name"] = custom_name + cfg = cp.ResticConfig.model_validate(base_config) + assert cfg.Name == custom_name + + @given(base_config=valid_unparsed_empty_restic_config()) def test_restic_config_uuid_is_mapname(base_config) -> None: cfg = cp.ResticConfig.model_validate(base_config)