Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/butter_backup/backup_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}.")
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/butter_backup/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/butter_backup/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions src/butter_backup/device_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
Expand Down
14 changes: 14 additions & 0 deletions tests/config_parser/test_btrfs_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion tests/config_parser/test_parse_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()}]")
Expand All @@ -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:
Expand All @@ -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,
)
Expand Down
16 changes: 16 additions & 0 deletions tests/config_parser/test_restic_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading