declarative-data-backup 0.4.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/PKG-INFO +7 -5
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/README.md +6 -4
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/pyproject.toml +1 -1
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/backup.py +52 -17
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/PKG-INFO +7 -5
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/tests/test_backup.py +10 -4
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/setup.cfg +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/__init__.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/__main__.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/__init__.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/archive_naming.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/borg.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/borg_cli.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/__init__.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/__init__.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/models.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/utils.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/yaml_parser.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/yaml_raw_loader.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/logging.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/environment.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/errors.py +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/SOURCES.txt +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/dependency_links.txt +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/requires.txt +0 -0
- {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: declarative-data-backup
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Declarative Borg-based data backups
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -246,10 +246,12 @@ The stable source series is derived from the canonical source path and includes
|
|
|
246
246
|
a SHA-256 suffix. Borg receives the canonical absolute path as the source, so
|
|
247
247
|
the original directory hierarchy is retained inside the archive.
|
|
248
248
|
|
|
249
|
-
The application
|
|
250
|
-
If a create
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
The application attempts every configured backup group before pruning any
|
|
250
|
+
source. If a group has a create failure or Borg reports warnings, pruning is
|
|
251
|
+
skipped for that group, but other groups that completed successfully are still
|
|
252
|
+
pruned. Failures are reported and make the overall run fail. Retries use a new
|
|
253
|
+
run UUID and may create another archive for that day; archives are never
|
|
254
|
+
overwritten to handle a collision.
|
|
253
255
|
|
|
254
256
|
`--keep-daily N` retains the latest archive in up to `N` daily buckets. It does
|
|
255
257
|
not guarantee `N` successful calendar days of recovery if scheduled runs fail
|
|
@@ -238,10 +238,12 @@ The stable source series is derived from the canonical source path and includes
|
|
|
238
238
|
a SHA-256 suffix. Borg receives the canonical absolute path as the source, so
|
|
239
239
|
the original directory hierarchy is retained inside the archive.
|
|
240
240
|
|
|
241
|
-
The application
|
|
242
|
-
If a create
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
The application attempts every configured backup group before pruning any
|
|
242
|
+
source. If a group has a create failure or Borg reports warnings, pruning is
|
|
243
|
+
skipped for that group, but other groups that completed successfully are still
|
|
244
|
+
pruned. Failures are reported and make the overall run fail. Retries use a new
|
|
245
|
+
run UUID and may create another archive for that day; archives are never
|
|
246
|
+
overwritten to handle a collision.
|
|
245
247
|
|
|
246
248
|
`--keep-daily N` retains the latest archive in up to `N` daily buckets. It does
|
|
247
249
|
not guarantee `N` successful calendar days of recovery if scheduled runs fail
|
|
@@ -56,38 +56,73 @@ def run_backup(
|
|
|
56
56
|
run_id = uuid.uuid4().hex
|
|
57
57
|
with repository_lock(settings.borg_base_dir / "backup.lock"):
|
|
58
58
|
created_at = datetime.now(UTC)
|
|
59
|
+
successful_definitions = []
|
|
60
|
+
failures: list[BackupError] = []
|
|
59
61
|
for definition in definitions:
|
|
60
62
|
logger.info(
|
|
61
63
|
"Starting backup group '%s' (%d sources).",
|
|
62
64
|
definition.name,
|
|
63
65
|
len(definition.sources),
|
|
64
66
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
try:
|
|
68
|
+
for folder in definition.sources:
|
|
69
|
+
backup_folder(
|
|
70
|
+
folder,
|
|
71
|
+
definition.compression,
|
|
72
|
+
definition.name,
|
|
73
|
+
settings,
|
|
74
|
+
created_at,
|
|
75
|
+
run_id,
|
|
76
|
+
definition.excludes,
|
|
77
|
+
)
|
|
78
|
+
except BackupError as exc:
|
|
79
|
+
logger.error(
|
|
80
|
+
"Backup group '%s' failed; retention pruning will be skipped "
|
|
81
|
+
"for this group: %s",
|
|
69
82
|
definition.name,
|
|
70
|
-
|
|
71
|
-
created_at,
|
|
72
|
-
run_id,
|
|
73
|
-
definition.excludes,
|
|
83
|
+
exc,
|
|
74
84
|
)
|
|
85
|
+
failures.append(exc)
|
|
86
|
+
continue
|
|
87
|
+
|
|
75
88
|
logger.info("Backup group '%s' completed.", definition.name)
|
|
89
|
+
successful_definitions.append(definition)
|
|
76
90
|
|
|
77
|
-
for definition in
|
|
91
|
+
for definition in successful_definitions:
|
|
78
92
|
logger.info(
|
|
79
93
|
"Starting retention pruning for group '%s' (%d sources).",
|
|
80
94
|
definition.name,
|
|
81
95
|
len(definition.sources),
|
|
82
96
|
)
|
|
97
|
+
pruning_succeeded = True
|
|
83
98
|
for folder in definition.sources:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
try:
|
|
100
|
+
prune(
|
|
101
|
+
folder,
|
|
102
|
+
definition.retention_days,
|
|
103
|
+
definition.name,
|
|
104
|
+
settings,
|
|
105
|
+
)
|
|
106
|
+
except BackupError as exc:
|
|
107
|
+
logger.error(
|
|
108
|
+
"Retention pruning failed for group '%s' source '%s': %s",
|
|
109
|
+
definition.name,
|
|
110
|
+
folder,
|
|
111
|
+
exc,
|
|
112
|
+
)
|
|
113
|
+
failures.append(exc)
|
|
114
|
+
pruning_succeeded = False
|
|
115
|
+
|
|
116
|
+
if pruning_succeeded:
|
|
117
|
+
logger.info(
|
|
118
|
+
"Retention pruning for group '%s' completed.", definition.name
|
|
89
119
|
)
|
|
90
|
-
|
|
120
|
+
|
|
121
|
+
if failures:
|
|
122
|
+
message = "; ".join(str(failure) for failure in failures)
|
|
123
|
+
if any(isinstance(failure, BorgWarningError) for failure in failures):
|
|
124
|
+
raise BorgWarningError(message)
|
|
125
|
+
raise BackupError(message)
|
|
91
126
|
|
|
92
127
|
|
|
93
128
|
def main(environ: Mapping[str, str] | None = None) -> int:
|
|
@@ -111,8 +146,8 @@ def main(environ: Mapping[str, str] | None = None) -> int:
|
|
|
111
146
|
|
|
112
147
|
except BorgWarningError as exc:
|
|
113
148
|
logger.error(
|
|
114
|
-
"Backup
|
|
115
|
-
"pruning
|
|
149
|
+
"Backup failed because Borg reported warnings; "
|
|
150
|
+
"retention pruning may have completed for other groups: %s",
|
|
116
151
|
exc,
|
|
117
152
|
)
|
|
118
153
|
return 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: declarative-data-backup
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Declarative Borg-based data backups
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -246,10 +246,12 @@ The stable source series is derived from the canonical source path and includes
|
|
|
246
246
|
a SHA-256 suffix. Borg receives the canonical absolute path as the source, so
|
|
247
247
|
the original directory hierarchy is retained inside the archive.
|
|
248
248
|
|
|
249
|
-
The application
|
|
250
|
-
If a create
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
The application attempts every configured backup group before pruning any
|
|
250
|
+
source. If a group has a create failure or Borg reports warnings, pruning is
|
|
251
|
+
skipped for that group, but other groups that completed successfully are still
|
|
252
|
+
pruned. Failures are reported and make the overall run fail. Retries use a new
|
|
253
|
+
run UUID and may create another archive for that day; archives are never
|
|
254
|
+
overwritten to handle a collision.
|
|
253
255
|
|
|
254
256
|
`--keep-daily N` retains the latest archive in up to `N` daily buckets. It does
|
|
255
257
|
not guarantee `N` successful calendar days of recovery if scheduled runs fail
|
|
@@ -79,7 +79,7 @@ def test_main_runs_all_creates_before_any_prune(
|
|
|
79
79
|
assert "Retention pruning for group 'media_backup' completed." in caplog.text
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def
|
|
82
|
+
def test_main_warning_skips_failed_group_but_prunes_successful_groups(
|
|
83
83
|
settings_factory,
|
|
84
84
|
monkeypatch,
|
|
85
85
|
caplog,
|
|
@@ -91,14 +91,15 @@ def test_main_warning_aborts_remaining_creates_and_all_pruning(
|
|
|
91
91
|
second.mkdir()
|
|
92
92
|
_write_config(
|
|
93
93
|
settings,
|
|
94
|
-
f"
|
|
94
|
+
f"failed:\n folders: [{first}]\nsucceeded:\n folders: [{second}]\n",
|
|
95
95
|
)
|
|
96
96
|
_configure_preflight(monkeypatch)
|
|
97
97
|
calls = []
|
|
98
98
|
|
|
99
99
|
def warning(folder, *args):
|
|
100
100
|
calls.append(("create", folder))
|
|
101
|
-
|
|
101
|
+
if folder == first:
|
|
102
|
+
raise BorgWarningError(f"warning for {folder}")
|
|
102
103
|
|
|
103
104
|
monkeypatch.setattr(backup, "backup_folder", warning)
|
|
104
105
|
monkeypatch.setattr(
|
|
@@ -109,7 +110,11 @@ def test_main_warning_aborts_remaining_creates_and_all_pruning(
|
|
|
109
110
|
caplog.set_level(logging.ERROR)
|
|
110
111
|
|
|
111
112
|
assert backup.main(_settings_environment(settings)) == 1
|
|
112
|
-
assert calls == [
|
|
113
|
+
assert calls == [
|
|
114
|
+
("create", first),
|
|
115
|
+
("create", second),
|
|
116
|
+
("prune", second),
|
|
117
|
+
]
|
|
113
118
|
assert "warnings" in caplog.text
|
|
114
119
|
assert str(first) in caplog.text
|
|
115
120
|
|
|
@@ -269,6 +274,7 @@ def test_main_prune_failure_is_nonzero_and_scoped(
|
|
|
269
274
|
("create", first),
|
|
270
275
|
("create", second),
|
|
271
276
|
("prune", first),
|
|
277
|
+
("prune", second),
|
|
272
278
|
]
|
|
273
279
|
|
|
274
280
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|