Skip to content

Using the Python library

The library lets you drive ezbak from your own code: a management script, a scheduled job, or a larger application. Build a BackupConfig, pass it to EZBak, and call the backup methods.

A first backup

from pathlib import Path
from ezbak import EZBak, BackupConfig

backups = EZBak(
    BackupConfig(
        name="my-backup",
        source_paths=[Path("/data")],
        storage_paths=[Path("/backups")],
        keep_last=10,
    )
)

backups.create_backup()
print([backup.name for backup in backups.list_backups()])
backups.prune_backups()

BackupConfig validates on construction. A missing name or storage location raises pydantic.ValidationError. Every field is in the configuration reference.

The ezbak() shortcut

For quick scripts, ezbak(**kwargs) builds the configuration for you. These two lines are equivalent:

from ezbak import EZBak, BackupConfig, ezbak

backups = ezbak(name="my-backup", source_paths=["/data"], storage_paths=["/backups"])
backups = EZBak(BackupConfig(name="my-backup", source_paths=["/data"], storage_paths=["/backups"]))

When you want an explicit, reusable configuration, prefer EZBak(BackupConfig(...)).

Restore

restore_backup() restores the latest backup by default. Pass a restore_path, or set it on the configuration.

backups.restore_backup(restore_path="/restore")

To restore an older backup, select it with get_backup_as_of() and pass it to restore_backup():

backup = backups.get_backup_as_of("20241201")
if backup:
    backups.restore_backup(restore_path="/restore", backup=backup)

get_backup_as_of(point_in_time) returns the newest backup at or before the end of the period you name. An explicit backup argument takes priority over a configured restore_date, which in turn takes priority over the latest backup.

Preview a prune

prune_backups(dry_run=True) returns the backups that the keep rules no longer keep. It deletes none of them:

would_delete = backups.prune_backups(dry_run=True)
print(f"Would delete {len(would_delete)} backups")

Handle failures

Every ezbak error subclasses EZBakError. A backup that cannot write to a storage location raises BackupFailedError, and it still keeps the copies that succeeded.

from ezbak.exceptions import BackupFailedError

try:
    backups.create_backup()
except BackupFailedError as error:
    print(f"Failed storage locations: {error.failed_storage_locations}")
    print(f"Backups that succeeded: {[b.name for b in error.created_backups]}")

A restore that cannot download or extract an archive raises RestoreFailedError:

from ezbak.exceptions import RestoreFailedError

try:
    backups.restore_backup(restore_path="/restore")
except RestoreFailedError as error:
    print(f"Restore failed: {error}")

restore_backup() returns a RestoreOutcome member and raises nothing for two cases that are not errors. It returns RestoreOutcome.NO_BACKUP when there is no backup to restore. It returns RestoreOutcome.SKIPPED_POPULATED when skip_restore_if_populated is set and the target already holds data.

A library caller reads the return value and decides what to do. skip_if_no_backup and skip_restore_if_populated therefore matter mainly to the CLI and the container, which turn those results into an exit code. See Failure behavior and the full Python API reference.