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.
Files changed (26) hide show
  1. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/PKG-INFO +7 -5
  2. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/README.md +6 -4
  3. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/pyproject.toml +1 -1
  4. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/backup.py +52 -17
  5. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/PKG-INFO +7 -5
  6. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/tests/test_backup.py +10 -4
  7. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/setup.cfg +0 -0
  8. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/__init__.py +0 -0
  9. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/__main__.py +0 -0
  10. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/__init__.py +0 -0
  11. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/archive_naming.py +0 -0
  12. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/borg.py +0 -0
  13. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/borg/borg_cli.py +0 -0
  14. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/__init__.py +0 -0
  15. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/__init__.py +0 -0
  16. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/models.py +0 -0
  17. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/utils.py +0 -0
  18. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/backup_config/yaml_parser.py +0 -0
  19. {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
  20. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/config/logging.py +0 -0
  21. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/environment.py +0 -0
  22. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup/errors.py +0 -0
  23. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/SOURCES.txt +0 -0
  24. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/dependency_links.txt +0 -0
  25. {declarative_data_backup-0.4.0 → declarative_data_backup-0.4.1}/src/declarative_data_backup.egg-info/requires.txt +0 -0
  26. {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.0
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 creates all configured archives before pruning any source.
250
- If a create fails or Borg reports warnings, the run stops and no pruning is
251
- performed. Retries use a new run UUID and may create another archive for that
252
- day; archives are never overwritten to handle a collision.
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 creates all configured archives before pruning any source.
242
- If a create fails or Borg reports warnings, the run stops and no pruning is
243
- performed. Retries use a new run UUID and may create another archive for that
244
- day; archives are never overwritten to handle a collision.
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "declarative-data-backup"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Declarative Borg-based data backups"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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
- for folder in definition.sources:
66
- backup_folder(
67
- folder,
68
- definition.compression,
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
- settings,
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 definitions:
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
- prune(
85
- folder,
86
- definition.retention_days,
87
- definition.name,
88
- settings,
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
- logger.info("Retention pruning for group '%s' completed.", definition.name)
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 stopped because Borg reported warnings; "
115
- "pruning was not completed: %s",
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.0
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 creates all configured archives before pruning any source.
250
- If a create fails or Borg reports warnings, the run stops and no pruning is
251
- performed. Retries use a new run UUID and may create another archive for that
252
- day; archives are never overwritten to handle a collision.
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 test_main_warning_aborts_remaining_creates_and_all_pruning(
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"media:\n folders: [{first}, {second}]\n",
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
- raise BorgWarningError(f"warning for {folder}")
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 == [("create", first)]
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