duplicity 3.0.6.dev11__tar.gz → 3.0.6.dev12__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.
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/CHANGELOG.md +19 -1
- {duplicity-3.0.6.dev11/duplicity.egg-info → duplicity-3.0.6.dev12}/PKG-INFO +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/__init__.py +2 -2
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backend.py +4 -2
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/dpbxbackend.py +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/gdocsbackend.py +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/webdavbackend.py +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/xorrisobackend.py +5 -5
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_main.py +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/diffdir.py +6 -6
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_collections.py +27 -22
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_main.py +13 -8
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_temp.py +3 -3
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_time.py +18 -14
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/file_naming.py +25 -12
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/globmatch.py +2 -2
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/gpg.py +18 -9
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/lazy.py +2 -2
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/librsync.py +4 -4
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/manifest.py +4 -4
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/patchdir.py +18 -12
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/path.py +21 -17
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/selection.py +8 -8
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/statistics.py +3 -3
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12/duplicity.egg-info}/PKG-INFO +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/man/duplicity.1 +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/pyproject.toml +2 -19
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/setup.py +1 -1
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/AUTHORS.md +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/COPYING +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-LOG.md +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-REPO.md +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-TESTING.md +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README.md +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/__main__.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/_librsyncmodule.c +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/argparse311.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backend_pool.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/__init__.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_cf_cloudfiles.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_cf_pyrax.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_testbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/adbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/azurebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/b2backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/boxbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/cfbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/gdrivebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/giobackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/hsibackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/hubicbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/idrivedbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/imapbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/jottacloudbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/lftpbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/localbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/mediafirebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megabackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megav2backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megav3backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/multibackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ncftpbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/onedrivebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/par2backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pcabackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pydrivebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pyrax_identity/__init__.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pyrax_identity/hubic.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/rclonebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/rsyncbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/s3_boto3_backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/slatebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ssh_paramiko_backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ssh_pexpect_backend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/swiftbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/sxbackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/tahoebackend.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cached_ops.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_data.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_util.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/config.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_tarfile.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/errors.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/filechunkio.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/gpginterface.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/log.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/progress.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/robust.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/tempdir.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/util.py +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/SOURCES.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/dependency_links.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/entry_points.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/requires.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/top_level.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/af_ZA/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ar_SA/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ca_ES/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/cs_CZ/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/da_DK/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/de_AT/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/de_DE/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/el_GR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_AU/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_GB/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_PR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_US/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_EM/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_ES/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_MX/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_PR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_US/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/fi_FI/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/fr_FR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/he_IL/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/hu_HU/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/it_IT/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ja_JP/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ko_KR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_BE/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_NL/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_SR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/no_NO/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pl_PL/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pt_BR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pt_PT/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ro_RO/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_BY/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_MD/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_RU/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_UA/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/sr_SP/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/sv_SE/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/tr_TR/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/uk_UA/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/vi_VN/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_CN/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_HK/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_MO/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_SG/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_TW/duplicity.mo +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/requirements.txt +0 -0
- {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/setup.cfg +0 -0
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
##
|
|
4
|
+
## (unreleased)
|
|
5
|
+
|
|
6
|
+
### Changes
|
|
7
|
+
|
|
8
|
+
* Misc build tools upgrades. [Kenneth Loafman]
|
|
9
|
+
|
|
10
|
+
* Add error message to raw assert statements. [Kenneth Loafman]
|
|
11
|
+
|
|
12
|
+
### Fix
|
|
13
|
+
|
|
14
|
+
* 'duplicity incremental' attempts to fetch remote manifest which is not
|
|
15
|
+
needed for the current backup. [Kenneth Loafman]
|
|
16
|
+
|
|
17
|
+
Fixes #891
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## rel.3.0.6.dev11 (2025-10-19)
|
|
5
21
|
|
|
6
22
|
### New
|
|
7
23
|
|
|
@@ -9,6 +25,8 @@
|
|
|
9
25
|
|
|
10
26
|
### Changes
|
|
11
27
|
|
|
28
|
+
* Update CHANGELOG.md. [Kenneth Loafman]
|
|
29
|
+
|
|
12
30
|
* Remove may fail py3.14. [Kenneth Loafman]
|
|
13
31
|
|
|
14
32
|
* Use python:314 not -rc. [Kenneth Loafman]
|
|
@@ -677,7 +677,9 @@ class BackendWrapper(object):
|
|
|
677
677
|
"""
|
|
678
678
|
Delete each filename in filename_list, in order if possible.
|
|
679
679
|
"""
|
|
680
|
-
assert not isinstance(
|
|
680
|
+
assert not isinstance(
|
|
681
|
+
filename_list, bytes
|
|
682
|
+
), "filename_list must be an iterable of filenames (not a byte string)"
|
|
681
683
|
if hasattr(self.backend, "_delete_list"):
|
|
682
684
|
self._do_delete_list(filename_list)
|
|
683
685
|
elif hasattr(self.backend, "_delete"):
|
|
@@ -777,5 +779,5 @@ class BackendWrapper(object):
|
|
|
777
779
|
"""
|
|
778
780
|
fin = self.get_fileobj_read(filename, parseresults)
|
|
779
781
|
buf = fin.read()
|
|
780
|
-
assert not fin.close()
|
|
782
|
+
assert not fin.close(), "fin failed to close"
|
|
781
783
|
return buf
|
|
@@ -307,7 +307,7 @@ Exception: {str(e)}"""
|
|
|
307
307
|
retry_number = config.num_retries
|
|
308
308
|
|
|
309
309
|
if not is_eof:
|
|
310
|
-
assert len(buf) != 0
|
|
310
|
+
assert len(buf) != 0, "buffer should not be empty"
|
|
311
311
|
log.Debug(
|
|
312
312
|
f"dpbx,files_upload_sesssion_append([{len(buf)} bytes], "
|
|
313
313
|
f"offset={int(upload_cursor.offset)})"
|
|
@@ -112,7 +112,7 @@ class GDocsBackend(duplicity.backend.Backend):
|
|
|
112
112
|
f"Failed to initialize upload of file '{source_path.get_filename()}' "
|
|
113
113
|
f"to remote folder '{self.folder.title.text}'"
|
|
114
114
|
)
|
|
115
|
-
assert not file.close()
|
|
115
|
+
assert not file.close(), "file failed to close"
|
|
116
116
|
|
|
117
117
|
def _get(self, remote_filename, local_path):
|
|
118
118
|
entries = self._fetch_entries(
|
|
@@ -428,7 +428,7 @@ class WebDAVBackend(duplicity.backend.Backend):
|
|
|
428
428
|
response = self.request("GET", url)
|
|
429
429
|
if response.status == 200:
|
|
430
430
|
shutil.copyfileobj(response, target_file)
|
|
431
|
-
assert not target_file.close()
|
|
431
|
+
assert not target_file.close(), "target_file failed to close"
|
|
432
432
|
response.close()
|
|
433
433
|
else:
|
|
434
434
|
status = response.status
|
|
@@ -254,7 +254,7 @@ class Xorriso:
|
|
|
254
254
|
"""
|
|
255
255
|
Copy file to the ISO image. Does not commit the changes yet.
|
|
256
256
|
"""
|
|
257
|
-
assert isinstance(files, list)
|
|
257
|
+
assert isinstance(files, list), "should be a list of files"
|
|
258
258
|
|
|
259
259
|
stdout, stderr = self.__send_cmd(
|
|
260
260
|
"-cpr",
|
|
@@ -270,7 +270,7 @@ class Xorriso:
|
|
|
270
270
|
"""
|
|
271
271
|
Remove a list of files from the image. Does not commit the changes yet.
|
|
272
272
|
"""
|
|
273
|
-
assert isinstance(files, list)
|
|
273
|
+
assert isinstance(files, list), "should be a list of files"
|
|
274
274
|
|
|
275
275
|
if not files:
|
|
276
276
|
return
|
|
@@ -285,9 +285,9 @@ class Xorriso:
|
|
|
285
285
|
"""
|
|
286
286
|
Extract files from the ISO image.
|
|
287
287
|
"""
|
|
288
|
-
assert isinstance(files, list)
|
|
288
|
+
assert isinstance(files, list), "should be a list of files"
|
|
289
289
|
|
|
290
|
-
assert not os.path.exists(dest) or os.path.isfile(dest)
|
|
290
|
+
assert not os.path.exists(dest) or os.path.isfile(dest), "dest must be a directory"
|
|
291
291
|
|
|
292
292
|
if len(files) == 0:
|
|
293
293
|
return
|
|
@@ -365,7 +365,7 @@ class XorrisoBackend(duplicity.backend.Backend):
|
|
|
365
365
|
self.xorriso = Xorriso(device=self.device, xorriso_path=xorriso_cmd, xorriso_args=self.xorriso_args)
|
|
366
366
|
|
|
367
367
|
def _put(self, source_path, remote_filename):
|
|
368
|
-
assert not os.path.isdir(source_path.name.decode("utf8"))
|
|
368
|
+
assert not os.path.isdir(source_path.name.decode("utf8")), "source_path must be a file"
|
|
369
369
|
source_path.setdata()
|
|
370
370
|
source_size = source_path.getsize()
|
|
371
371
|
progress.report_transfer(0, source_size)
|
|
@@ -317,7 +317,7 @@ def process_command_line(cmdline_list):
|
|
|
317
317
|
log.ErrorCode.verify_dir_doesnt_exist,
|
|
318
318
|
)
|
|
319
319
|
else:
|
|
320
|
-
assert config.action in ("full", "inc")
|
|
320
|
+
assert config.action in ("full", "inc"), "action must be full or inc"
|
|
321
321
|
if not local_path.exists():
|
|
322
322
|
log.FatalError(
|
|
323
323
|
_(f"Backup source directory {local_path.uc_name} does not exist."),
|
|
@@ -111,7 +111,7 @@ def get_delta_path(new_path, sig_path, sigTarFile=None):
|
|
|
111
111
|
Return new delta_path which, when read, writes sig to sig_fileobj,
|
|
112
112
|
if sigTarFile is not None
|
|
113
113
|
"""
|
|
114
|
-
assert new_path
|
|
114
|
+
assert new_path, "new_path must not be None or falsy when computing delta"
|
|
115
115
|
if sigTarFile:
|
|
116
116
|
ti = new_path.get_tarinfo()
|
|
117
117
|
index = new_path.index
|
|
@@ -502,7 +502,7 @@ class TarBlockIter(object):
|
|
|
502
502
|
"""
|
|
503
503
|
Turn next value of input_iter into a TarBlock
|
|
504
504
|
"""
|
|
505
|
-
assert not self.process_waiting
|
|
505
|
+
assert not self.process_waiting, "process_waiting should be False before starting a new process"
|
|
506
506
|
raise NotImplementedError("'process' not implemented.")
|
|
507
507
|
|
|
508
508
|
def process_continued(self):
|
|
@@ -512,7 +512,7 @@ class TarBlockIter(object):
|
|
|
512
512
|
If processing val above would produce more than one TarBlock,
|
|
513
513
|
get the rest of them by calling process_continue.
|
|
514
514
|
"""
|
|
515
|
-
assert self.process_waiting
|
|
515
|
+
assert self.process_waiting, "process_continued called without a waiting process to continue"
|
|
516
516
|
raise NotImplementedError("'process_continues' not implemented.")
|
|
517
517
|
|
|
518
518
|
def __next__(self):
|
|
@@ -662,7 +662,7 @@ class DeltaTarBlockIter(TarBlockIter):
|
|
|
662
662
|
if not delta_ropath.type:
|
|
663
663
|
add_prefix(ti, r"deleted")
|
|
664
664
|
else:
|
|
665
|
-
assert delta_ropath.difftype == "snapshot"
|
|
665
|
+
assert delta_ropath.difftype == "snapshot", "Expected snapshot difftype for fileless delta_ropath"
|
|
666
666
|
add_prefix(ti, r"snapshot")
|
|
667
667
|
return self.tarinfo2tarblock(index, ti)
|
|
668
668
|
|
|
@@ -707,7 +707,7 @@ class DeltaTarBlockIter(TarBlockIter):
|
|
|
707
707
|
"""
|
|
708
708
|
Return next volume in multivol diff or snapshot
|
|
709
709
|
"""
|
|
710
|
-
assert self.process_waiting
|
|
710
|
+
assert self.process_waiting, "No multivolume process is waiting; cannot continue"
|
|
711
711
|
ropath = self.process_ropath
|
|
712
712
|
ti, index = ropath.get_tarinfo(), ropath.index
|
|
713
713
|
ti.name = f"{self.process_prefix}/{int(self.process_next_vol_number)}"
|
|
@@ -738,7 +738,7 @@ def write_block_iter(block_iter, out_obj):
|
|
|
738
738
|
for block in block_iter:
|
|
739
739
|
fp.write(block.data)
|
|
740
740
|
fp.write(block_iter.get_footer())
|
|
741
|
-
assert not fp.close()
|
|
741
|
+
assert not fp.close(), "fp failed to close"
|
|
742
742
|
if isinstance(out_obj, Path):
|
|
743
743
|
out_obj.setdata()
|
|
744
744
|
|
|
@@ -138,7 +138,9 @@ class BackupSet(object):
|
|
|
138
138
|
elif pr.manifest:
|
|
139
139
|
self.set_manifest(filename)
|
|
140
140
|
else:
|
|
141
|
-
assert
|
|
141
|
+
assert (
|
|
142
|
+
pr.volume_number is not None
|
|
143
|
+
), f"Missing volume_number in ParseResults for file {os.fsdecode(filename)}"
|
|
142
144
|
assert pr.volume_number not in self.volume_name_dict, (
|
|
143
145
|
f"Volume {int(pr.volume_number)} is already in the volume list as "
|
|
144
146
|
f"{os.fsdecode(self.volume_name_dict[pr.volume_number])}. "
|
|
@@ -156,7 +158,7 @@ class BackupSet(object):
|
|
|
156
158
|
@param pr: parse results
|
|
157
159
|
@type pr: ParseResults
|
|
158
160
|
"""
|
|
159
|
-
assert not self.info_set
|
|
161
|
+
assert not self.info_set, "BackupSet info already set; cannot set twice"
|
|
160
162
|
self.type = pr.type
|
|
161
163
|
self.time = pr.time
|
|
162
164
|
self.start_time = pr.start_time
|
|
@@ -198,9 +200,6 @@ class BackupSet(object):
|
|
|
198
200
|
"""
|
|
199
201
|
Add local and remote manifest filenames to backup set
|
|
200
202
|
"""
|
|
201
|
-
assert (
|
|
202
|
-
not self.remote_manifest_name
|
|
203
|
-
), f"Cannot set filename of remote manifest to {remote_filename}; already set to {self.remote_manifest_name}."
|
|
204
203
|
self.remote_manifest_name = remote_filename
|
|
205
204
|
|
|
206
205
|
local_filename_list = config.archive_dir_path.listdir()
|
|
@@ -302,7 +301,7 @@ class BackupSet(object):
|
|
|
302
301
|
"""
|
|
303
302
|
Return manifest object by reading local manifest file
|
|
304
303
|
"""
|
|
305
|
-
assert self.local_manifest_path
|
|
304
|
+
assert self.local_manifest_path, "Local manifest path is not set for this BackupSet"
|
|
306
305
|
manifest_buffer = self.local_manifest_path.get_data()
|
|
307
306
|
log.Info(_(f"Processing local manifest {self.local_manifest_path.uc_name} ({len(manifest_buffer)})"))
|
|
308
307
|
return manifest.Manifest().from_string(manifest_buffer)
|
|
@@ -353,7 +352,7 @@ class BackupSet(object):
|
|
|
353
352
|
"""
|
|
354
353
|
Return sorted list of (remote) filenames of files in set
|
|
355
354
|
"""
|
|
356
|
-
assert self.info_set
|
|
355
|
+
assert self.info_set, "BackupSet info must be set before retrieving filenames"
|
|
357
356
|
volume_num_list = sorted(self.volume_name_dict.keys())
|
|
358
357
|
volume_filenames = [self.volume_name_dict[x] for x in volume_num_list]
|
|
359
358
|
if self.remote_manifest_name:
|
|
@@ -421,9 +420,11 @@ class BackupChain(object):
|
|
|
421
420
|
"""
|
|
422
421
|
Add full backup set
|
|
423
422
|
"""
|
|
424
|
-
assert not self.fullset and isinstance(
|
|
423
|
+
assert not self.fullset and isinstance(
|
|
424
|
+
fullset, BackupSet
|
|
425
|
+
), "Full backup set must be unset and argument must be a BackupSet instance"
|
|
425
426
|
self.fullset = fullset
|
|
426
|
-
assert fullset.time
|
|
427
|
+
assert fullset.time, "Full backup set must have a valid 'time' attribute"
|
|
427
428
|
self.start_time, self.end_time = fullset.time, fullset.time
|
|
428
429
|
|
|
429
430
|
def add_inc(self, incset):
|
|
@@ -457,7 +458,7 @@ class BackupChain(object):
|
|
|
457
458
|
dup_time.timetopretty(incset.end_time),
|
|
458
459
|
)
|
|
459
460
|
)
|
|
460
|
-
assert self.end_time
|
|
461
|
+
assert self.end_time, "BackupChain end_time must be set after adding an incremental set"
|
|
461
462
|
return True
|
|
462
463
|
|
|
463
464
|
def delete(self, keep_full=False):
|
|
@@ -654,7 +655,7 @@ class SignatureChain(object):
|
|
|
654
655
|
Return ordered list of signature fileobjs opened for reading,
|
|
655
656
|
optionally at a certain time
|
|
656
657
|
"""
|
|
657
|
-
assert self.fullsig
|
|
658
|
+
assert self.fullsig, "SignatureChain fullsig must be set before accessing related data"
|
|
658
659
|
if self.archive_dir_path: # local
|
|
659
660
|
|
|
660
661
|
def filename_to_fileobj(filename):
|
|
@@ -677,7 +678,7 @@ class SignatureChain(object):
|
|
|
677
678
|
if not keep_full:
|
|
678
679
|
self.archive_dir_path.append(self.fullsig).delete()
|
|
679
680
|
else:
|
|
680
|
-
assert self.backend
|
|
681
|
+
assert self.backend, "Backend must be set before performing this operation"
|
|
681
682
|
inclist_copy = self.inclist[:]
|
|
682
683
|
inclist_copy.reverse()
|
|
683
684
|
if not keep_full:
|
|
@@ -883,7 +884,7 @@ class CollectionsStatus(object):
|
|
|
883
884
|
"""
|
|
884
885
|
Log various error messages if find incomplete/orphaned files
|
|
885
886
|
"""
|
|
886
|
-
assert self.values_set
|
|
887
|
+
assert self.values_set, "CollectionsStatus values must be set (call set_values) before warning/reporting"
|
|
887
888
|
|
|
888
889
|
def missing_to_log_info(s):
|
|
889
890
|
"""
|
|
@@ -1013,7 +1014,7 @@ class CollectionsStatus(object):
|
|
|
1013
1014
|
chains.append(new_chain)
|
|
1014
1015
|
log.Debug(_("Found backup chain %s") % (new_chain.short_desc()))
|
|
1015
1016
|
else:
|
|
1016
|
-
assert set.type == "inc"
|
|
1017
|
+
assert set.type == "inc", f"Expected incremental set type 'inc', got {set.type!r} for set {set}"
|
|
1017
1018
|
for chain in chains:
|
|
1018
1019
|
if chain.add_inc(set):
|
|
1019
1020
|
log.Debug(_("Added set %s to pre-existing chain %s") % (set.get_timestr(), chain.short_desc()))
|
|
@@ -1079,7 +1080,9 @@ class CollectionsStatus(object):
|
|
|
1079
1080
|
if pr:
|
|
1080
1081
|
if pr.type == "full-sig":
|
|
1081
1082
|
new_chain = get_new_sigchain()
|
|
1082
|
-
assert new_chain.add_filename(
|
|
1083
|
+
assert new_chain.add_filename(
|
|
1084
|
+
filename, pr
|
|
1085
|
+
), f"Failed to add signature file {os.fsdecode(filename)} to new signature chain"
|
|
1083
1086
|
chains.append(new_chain)
|
|
1084
1087
|
elif pr.type == "new-sig":
|
|
1085
1088
|
new_sig_filenames.append(filename)
|
|
@@ -1116,7 +1119,7 @@ class CollectionsStatus(object):
|
|
|
1116
1119
|
if len(chain_list) == 1:
|
|
1117
1120
|
sorted_chain_list.append(chain_list[0])
|
|
1118
1121
|
else:
|
|
1119
|
-
assert len(chain_list) == 2
|
|
1122
|
+
assert len(chain_list) == 2, f"Expected exactly 2 chains with equal end_time, got {len(chain_list)}"
|
|
1120
1123
|
if chain_list[0].backend: # is remote, goes first
|
|
1121
1124
|
sorted_chain_list.append(chain_list[0])
|
|
1122
1125
|
sorted_chain_list.append(chain_list[1])
|
|
@@ -1190,7 +1193,9 @@ class CollectionsStatus(object):
|
|
|
1190
1193
|
recognizable as a duplicity file, but isn't part of some
|
|
1191
1194
|
complete backup set, or current signature chain.
|
|
1192
1195
|
"""
|
|
1193
|
-
assert
|
|
1196
|
+
assert (
|
|
1197
|
+
self.values_set
|
|
1198
|
+
), "CollectionsStatus values must be initialized via set_values() before calling get_extraneous()"
|
|
1194
1199
|
local_filenames = []
|
|
1195
1200
|
remote_filenames = []
|
|
1196
1201
|
ext_containers = self.orphaned_backup_sets + self.incomplete_backup_sets + self.missing_difftar_sets
|
|
@@ -1219,7 +1224,7 @@ class CollectionsStatus(object):
|
|
|
1219
1224
|
than t, and set B is an incremental based on A which is newer
|
|
1220
1225
|
than t, then the time of set A will not be returned.
|
|
1221
1226
|
"""
|
|
1222
|
-
assert self.values_set
|
|
1227
|
+
assert self.values_set, "CollectionsStatus values must be set before calling get_chains_older_than()"
|
|
1223
1228
|
old_chains = []
|
|
1224
1229
|
for chain in self.all_backup_chains:
|
|
1225
1230
|
if chain.end_time < t and (
|
|
@@ -1239,7 +1244,7 @@ class CollectionsStatus(object):
|
|
|
1239
1244
|
than t, and set B is an incremental based on A which is newer
|
|
1240
1245
|
than t, then the time of set A will not be returned.
|
|
1241
1246
|
"""
|
|
1242
|
-
assert self.values_set
|
|
1247
|
+
assert self.values_set, "CollectionsStatus values must be set before calling get_signature_chains_older_than()"
|
|
1243
1248
|
old_chains = []
|
|
1244
1249
|
for chain in self.all_sig_chains:
|
|
1245
1250
|
if chain.end_time < t and (
|
|
@@ -1283,8 +1288,8 @@ class CollectionsStatus(object):
|
|
|
1283
1288
|
a valid input). Thus the second-to-last is obtained with n=2
|
|
1284
1289
|
rather than n=1.
|
|
1285
1290
|
"""
|
|
1286
|
-
assert self.values_set
|
|
1287
|
-
assert n > 0
|
|
1291
|
+
assert self.values_set, "CollectionsStatus values must be set before calling get_nth_last_backup_chain()"
|
|
1292
|
+
assert n > 0, f"n must be > 0 (1 = latest chain); got n={n}"
|
|
1288
1293
|
|
|
1289
1294
|
if len(self.all_backup_chains) < n:
|
|
1290
1295
|
return None
|
|
@@ -1318,7 +1323,7 @@ class CollectionsStatus(object):
|
|
|
1318
1323
|
returns the times of sets which are old but part of the chains
|
|
1319
1324
|
where the newer end of the chain is newer than t.
|
|
1320
1325
|
"""
|
|
1321
|
-
assert self.values_set
|
|
1326
|
+
assert self.values_set, "CollectionsStatus values must be set before calling get_older_than_required()"
|
|
1322
1327
|
new_chains = [c for c in self.all_backup_chains if c.end_time >= t]
|
|
1323
1328
|
result_sets = []
|
|
1324
1329
|
for chain in new_chains:
|
|
@@ -580,7 +580,7 @@ def get_man_fileobj(backup_type):
|
|
|
580
580
|
@rtype: fileobj
|
|
581
581
|
@return: fileobj opened for writing
|
|
582
582
|
"""
|
|
583
|
-
assert backup_type == "full" or backup_type == "inc"
|
|
583
|
+
assert backup_type == "full" or backup_type == "inc", f"backup_type must be 'full' or 'inc', got {backup_type!r}"
|
|
584
584
|
|
|
585
585
|
part_man_filename = file_naming.get(backup_type, manifest=True, partial=True)
|
|
586
586
|
perm_man_filename = file_naming.get(backup_type, manifest=True)
|
|
@@ -605,7 +605,7 @@ def get_sig_fileobj(sig_type):
|
|
|
605
605
|
@rtype: fileobj
|
|
606
606
|
@return: fileobj opened for writing
|
|
607
607
|
"""
|
|
608
|
-
assert sig_type in ["full-sig", "new-sig"]
|
|
608
|
+
assert sig_type in ["full-sig", "new-sig"], f"sig_type must be 'full-sig' or 'new-sig', got {sig_type!r}"
|
|
609
609
|
|
|
610
610
|
part_sig_filename = file_naming.get(sig_type, gzipped=False, partial=True)
|
|
611
611
|
perm_sig_filename = file_naming.get(sig_type, gzipped=True)
|
|
@@ -630,7 +630,7 @@ def get_stat_fileobj(stat_type):
|
|
|
630
630
|
@rtype: fileobj
|
|
631
631
|
@return: fileobj opened for writing
|
|
632
632
|
"""
|
|
633
|
-
assert stat_type in ["full-stat", "inc-stat"]
|
|
633
|
+
assert stat_type in ["full-stat", "inc-stat"], f"stat_type must be 'full-stat' or 'inc-stat', got {stat_type!r}"
|
|
634
634
|
|
|
635
635
|
part_stat_filename = file_naming.get(stat_type, gzipped=False, partial=True)
|
|
636
636
|
perm_stat_filename = file_naming.get(stat_type, gzipped=True)
|
|
@@ -894,7 +894,10 @@ def restore_get_patched_rop_iter(col_stats):
|
|
|
894
894
|
index = ()
|
|
895
895
|
time = config.restore_time or dup_time.curtime
|
|
896
896
|
backup_chain = col_stats.get_backup_chain_at_time(time)
|
|
897
|
-
assert backup_chain,
|
|
897
|
+
assert backup_chain, (
|
|
898
|
+
f"No backup chain found for restore time {dup_time.timetostring(time)}; "
|
|
899
|
+
f"available chains: {col_stats.all_backup_chains}"
|
|
900
|
+
)
|
|
898
901
|
backup_setlist = backup_chain.get_sets_at_time(time)
|
|
899
902
|
num_vols = 0
|
|
900
903
|
for s in backup_setlist:
|
|
@@ -1113,7 +1116,9 @@ def remove_all_but_n_full(col_stats):
|
|
|
1113
1116
|
@rtype: void
|
|
1114
1117
|
@return: void
|
|
1115
1118
|
"""
|
|
1116
|
-
assert
|
|
1119
|
+
assert (
|
|
1120
|
+
config.keep_chains is not None
|
|
1121
|
+
), "config.keep_chains (from --keep-chains) must be set before removing old backups"
|
|
1117
1122
|
|
|
1118
1123
|
config.remove_time = col_stats.get_nth_last_full_backup_time(config.keep_chains)
|
|
1119
1124
|
|
|
@@ -1130,7 +1135,7 @@ def remove_old(col_stats):
|
|
|
1130
1135
|
@rtype: void
|
|
1131
1136
|
@return: void
|
|
1132
1137
|
"""
|
|
1133
|
-
assert config.remove_time is not None
|
|
1138
|
+
assert config.remove_time is not None, "config.remove_time must be set before removing old backups"
|
|
1134
1139
|
|
|
1135
1140
|
def set_times_str(setlist):
|
|
1136
1141
|
"""Return string listing times of sets in setlist"""
|
|
@@ -1227,7 +1232,7 @@ def sync_archive(col_stats):
|
|
|
1227
1232
|
"""
|
|
1228
1233
|
if config.metadata_sync_mode == "full":
|
|
1229
1234
|
return True
|
|
1230
|
-
assert config.metadata_sync_mode == "partial"
|
|
1235
|
+
assert config.metadata_sync_mode == "partial", "metadata_sync_mode must be 'partial' when not in full sync mode"
|
|
1231
1236
|
parsed = file_naming.parse(filename)
|
|
1232
1237
|
try:
|
|
1233
1238
|
target_chain = col_stats.get_backup_chain_at_time(config.restore_time or dup_time.curtime)
|
|
@@ -1448,7 +1453,7 @@ def check_last_manifest(col_stats):
|
|
|
1448
1453
|
@rtype: void
|
|
1449
1454
|
@return: void
|
|
1450
1455
|
"""
|
|
1451
|
-
assert col_stats.all_backup_chains
|
|
1456
|
+
assert col_stats.all_backup_chains, "No backup chains found; cannot check last manifest without any chains present"
|
|
1452
1457
|
last_backup_set = col_stats.all_backup_chains[-1].get_last()
|
|
1453
1458
|
# check remote manifest only if we can decrypt it (see #1729796)
|
|
1454
1459
|
last_backup_set.check_manifests(check_remote=config.check_remote)
|
|
@@ -138,7 +138,7 @@ class TempDupPath(path.DupPath):
|
|
|
138
138
|
"""
|
|
139
139
|
Returns a fileobj. When that is closed, delete file
|
|
140
140
|
"""
|
|
141
|
-
assert mode == "rb"
|
|
141
|
+
assert mode == "rb", "wrong mode for open_with_delete"
|
|
142
142
|
fh = FileobjHooked(path.DupPath.open(self, mode))
|
|
143
143
|
fh.addhook(self.delete)
|
|
144
144
|
return fh
|
|
@@ -187,7 +187,7 @@ class FileobjHooked(object):
|
|
|
187
187
|
"""
|
|
188
188
|
We have achieved the first checkpoint, make file visible and permanent.
|
|
189
189
|
"""
|
|
190
|
-
assert not config.restart
|
|
190
|
+
assert not config.restart, "may not restart after checkpoint"
|
|
191
191
|
self.tdp.rename(self.dirpath.append(self.partname))
|
|
192
192
|
self.fileobj.flush()
|
|
193
193
|
del self.hooklist[0]
|
|
@@ -245,7 +245,7 @@ class FileobjHooked(object):
|
|
|
245
245
|
"""
|
|
246
246
|
Close fileobj, running hooks right afterwards
|
|
247
247
|
"""
|
|
248
|
-
assert not self.fileobj.close()
|
|
248
|
+
assert not self.fileobj.close(), "self.fileobj failed to close"
|
|
249
249
|
for hook in self.hooklist:
|
|
250
250
|
hook()
|
|
251
251
|
|
|
@@ -76,14 +76,14 @@ def setcurtime(time_in_secs=None):
|
|
|
76
76
|
"""Sets the current time in curtime and curtimestr"""
|
|
77
77
|
global curtime, curtimestr
|
|
78
78
|
t = time_in_secs or int(time.time())
|
|
79
|
-
assert isinstance(t, int)
|
|
79
|
+
assert isinstance(t, int), "curtime must be an integer"
|
|
80
80
|
curtime, curtimestr = t, timetostring(t)
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def setprevtime(time_in_secs):
|
|
84
84
|
"""Sets the previous time in prevtime and prevtimestr"""
|
|
85
85
|
global prevtime, prevtimestr
|
|
86
|
-
assert isinstance(time_in_secs, int), prevtime
|
|
86
|
+
assert isinstance(time_in_secs, int), "prevtime must be an integer"
|
|
87
87
|
prevtime, prevtimestr = time_in_secs, timetostring(time_in_secs)
|
|
88
88
|
|
|
89
89
|
|
|
@@ -111,12 +111,12 @@ def stringtotime(timestring):
|
|
|
111
111
|
# old format for filename time
|
|
112
112
|
year, month, day = list(map(int, date.split("-")))
|
|
113
113
|
hour, minute, second = list(map(int, daytime.split(config.time_separator)))
|
|
114
|
-
assert 1900 < year < 2100, year
|
|
115
|
-
assert 1 <= month <= 12
|
|
116
|
-
assert 1 <= day <= 31
|
|
117
|
-
assert 0 <= hour <= 23
|
|
118
|
-
assert 0 <= minute <= 59
|
|
119
|
-
assert 0 <= second <= 61 # leap seconds
|
|
114
|
+
assert 1900 < year < 2100, f"year {year} out of supported range (1901..2099)"
|
|
115
|
+
assert 1 <= month <= 12, f"month {month} out of range (1..12)"
|
|
116
|
+
assert 1 <= day <= 31, f"day {day} out of range (1..31)"
|
|
117
|
+
assert 0 <= hour <= 23, f"hour {hour} out of range (0..23)"
|
|
118
|
+
assert 0 <= minute <= 59, f"minute {minute} out of range (0..59)"
|
|
119
|
+
assert 0 <= second <= 61, f"second {second} out of range (0..61, allowing leap seconds)" # leap seconds
|
|
120
120
|
# We want to return the time in units of seconds since the
|
|
121
121
|
# epoch. Unfortunately the only functin that does this
|
|
122
122
|
# works in terms of the current timezone and we have a
|
|
@@ -230,8 +230,8 @@ def gettzd(dstflag):
|
|
|
230
230
|
return "Z" # time is already in UTC
|
|
231
231
|
|
|
232
232
|
hours, minutes = list(map(abs, divmod(offset, 60)))
|
|
233
|
-
assert 0 <= hours <= 23
|
|
234
|
-
assert 0 <= minutes <= 59
|
|
233
|
+
assert 0 <= hours <= 23, f"hours component {hours} out of range (0..23)"
|
|
234
|
+
assert 0 <= minutes <= 59, f"minutes component {minutes} out of range (0..59)"
|
|
235
235
|
return f"{prefix}{int(hours):02}{config.time_separator}{int(minutes):02}"
|
|
236
236
|
|
|
237
237
|
|
|
@@ -239,8 +239,12 @@ def tzdtoseconds(tzd):
|
|
|
239
239
|
"""Given w3 compliant TZD, return how far ahead UTC is"""
|
|
240
240
|
if tzd == "Z":
|
|
241
241
|
return 0
|
|
242
|
-
assert
|
|
243
|
-
|
|
242
|
+
assert (
|
|
243
|
+
len(tzd) == 6
|
|
244
|
+
), "TZD must be 6 characters in the form ±HH:MM (e.g., +08:00)" # only accept forms like +08:00 for now
|
|
245
|
+
assert (tzd[0] == "-" or tzd[0] == "+") and (
|
|
246
|
+
tzd[3] == config.time_separator
|
|
247
|
+
), "TZD must start with +/- and include the correct time separator at position 3 (±HH:MM)"
|
|
244
248
|
return -60 * (60 * int(tzd[:3]) + int(tzd[4:]))
|
|
245
249
|
|
|
246
250
|
|
|
@@ -248,10 +252,10 @@ def cmp(time1, time2):
|
|
|
248
252
|
"""Compare time1 and time2 and return -1, 0, or 1"""
|
|
249
253
|
if isinstance(time1, (str, string)):
|
|
250
254
|
time1 = stringtotime(time1)
|
|
251
|
-
assert time1 is not None
|
|
255
|
+
assert time1 is not None, "time1 string could not be parsed into a valid timestamp"
|
|
252
256
|
if isinstance(time2, (str, str)):
|
|
253
257
|
time2 = stringtotime(time2)
|
|
254
|
-
assert time2 is not None
|
|
258
|
+
assert time2 is not None, "time2 string could not be parsed into a valid timestamp"
|
|
255
259
|
|
|
256
260
|
if time1 < time2:
|
|
257
261
|
return -1
|
|
@@ -253,14 +253,16 @@ def get(
|
|
|
253
253
|
can be given with the full and inc types. If manifest is true the
|
|
254
254
|
filename is of a full or inc manifest file.
|
|
255
255
|
"""
|
|
256
|
-
assert dup_time.curtimestr
|
|
256
|
+
assert dup_time.curtimestr, "dup_time.curtimestr must be set before generating filenames (call setcurtime)"
|
|
257
257
|
if encrypted:
|
|
258
258
|
gzipped = False
|
|
259
259
|
suffix = get_suffix(encrypted, gzipped)
|
|
260
260
|
part_string = b".part" if partial else b""
|
|
261
261
|
if type == "full-sig" or type == "new-sig":
|
|
262
|
-
assert not volume_number and not manifest
|
|
263
|
-
assert not (
|
|
262
|
+
assert not volume_number and not manifest, "volume_number and manifest must not be set for signature files"
|
|
263
|
+
assert not (
|
|
264
|
+
volume_number and part_string
|
|
265
|
+
), "volume_number and partial (.part) cannot be combined for signature files"
|
|
264
266
|
if type == "full-sig":
|
|
265
267
|
return (
|
|
266
268
|
config.file_prefix
|
|
@@ -280,8 +282,10 @@ def get(
|
|
|
280
282
|
)
|
|
281
283
|
)
|
|
282
284
|
elif type == "full-stat" or type == "inc-stat":
|
|
283
|
-
assert not volume_number and not manifest
|
|
284
|
-
assert not (
|
|
285
|
+
assert not volume_number and not manifest, "volume_number and manifest must not be set for statistics files"
|
|
286
|
+
assert not (
|
|
287
|
+
volume_number and part_string
|
|
288
|
+
), "volume_number and partial (.part) cannot be combined for statistics files"
|
|
285
289
|
type_suffix = b"jsonstat"
|
|
286
290
|
if type == "full-stat":
|
|
287
291
|
main_name = b"duplicity-full"
|
|
@@ -299,8 +303,8 @@ def get(
|
|
|
299
303
|
+ b"%s.%s.%s%s%s" % (main_name, timestamp, type_suffix, part_string, suffix)
|
|
300
304
|
)
|
|
301
305
|
else:
|
|
302
|
-
assert volume_number or manifest
|
|
303
|
-
assert not (volume_number and manifest)
|
|
306
|
+
assert volume_number or manifest, "Either 'volume_number' must be provided or 'manifest' must be True"
|
|
307
|
+
assert not (volume_number and manifest), "'volume_number' and 'manifest' are mutually exclusive"
|
|
304
308
|
|
|
305
309
|
prefix = config.file_prefix
|
|
306
310
|
|
|
@@ -329,7 +333,7 @@ def get(
|
|
|
329
333
|
suffix,
|
|
330
334
|
)
|
|
331
335
|
else:
|
|
332
|
-
assert 0
|
|
336
|
+
assert 0, f"Unknown file type '{type}' for constructing name"
|
|
333
337
|
|
|
334
338
|
|
|
335
339
|
def parse(filename):
|
|
@@ -523,15 +527,24 @@ class ParseResults:
|
|
|
523
527
|
compressed=None,
|
|
524
528
|
partial=False,
|
|
525
529
|
):
|
|
526
|
-
assert type in ["full-sig", "new-sig", "inc", "full", "full-stat", "inc-stat"]
|
|
530
|
+
assert type in ["full-sig", "new-sig", "inc", "full", "full-stat", "inc-stat"], (
|
|
531
|
+
f"Invalid duplicity filename type '{type}'. Expected one of:"
|
|
532
|
+
f" ['full-sig', 'new-sig', 'inc', 'full', 'full-stat', 'inc-stat']"
|
|
533
|
+
)
|
|
527
534
|
|
|
528
535
|
self.type = type
|
|
529
536
|
if type in ["inc", "full"]:
|
|
530
|
-
assert manifest or volume_number
|
|
537
|
+
assert manifest or volume_number, (
|
|
538
|
+
"ParseResults for 'full' or 'inc' requires either 'manifest' flag to be True "
|
|
539
|
+
f"or a 'volume_number' integer. Got manifest={manifest}, volume_number={volume_number}."
|
|
540
|
+
)
|
|
531
541
|
if type in ["inc", "new-sig", "inc-stat"]:
|
|
532
|
-
assert start_time and end_time
|
|
542
|
+
assert start_time and end_time, (
|
|
543
|
+
f"ParseResults for type '{type}' requires both start_time and end_time; "
|
|
544
|
+
f"got start_time={start_time}, end_time={end_time}."
|
|
545
|
+
)
|
|
533
546
|
else:
|
|
534
|
-
assert time
|
|
547
|
+
assert time, f"ParseResults for type '{type}' requires a single 'time' value; got time={time}."
|
|
535
548
|
|
|
536
549
|
self.manifest = manifest
|
|
537
550
|
self.volume_number = volume_number
|
|
@@ -79,7 +79,7 @@ def select_fn_from_glob(glob_str, include, ignore_case=False):
|
|
|
79
79
|
|
|
80
80
|
Note: including a folder implicitly includes everything within it.
|
|
81
81
|
"""
|
|
82
|
-
assert isinstance(glob_str, str)
|
|
82
|
+
assert isinstance(glob_str, str), f"glob_str must be a str (unicode), got {type(glob_str).__name__}: {glob_str!r}"
|
|
83
83
|
glob_ends_w_slash = False
|
|
84
84
|
|
|
85
85
|
if glob_str == "/":
|
|
@@ -170,7 +170,7 @@ def glob_to_regex(pat):
|
|
|
170
170
|
"""
|
|
171
171
|
# Internal. Used by glob_get_sf, glob_get_prefix_res and unit tests.
|
|
172
172
|
|
|
173
|
-
assert isinstance(pat, str)
|
|
173
|
+
assert isinstance(pat, str), f"pat must be a str (unicode), got {type(pat).__name__}: {pat!r}"
|
|
174
174
|
|
|
175
175
|
i, n, res = 0, len(pat), ""
|
|
176
176
|
while i < n:
|