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.
Files changed (143) hide show
  1. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/CHANGELOG.md +19 -1
  2. {duplicity-3.0.6.dev11/duplicity.egg-info → duplicity-3.0.6.dev12}/PKG-INFO +1 -1
  3. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/__init__.py +2 -2
  4. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backend.py +4 -2
  5. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/dpbxbackend.py +1 -1
  6. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/gdocsbackend.py +1 -1
  7. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/webdavbackend.py +1 -1
  8. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/xorrisobackend.py +5 -5
  9. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_main.py +1 -1
  10. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/diffdir.py +6 -6
  11. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_collections.py +27 -22
  12. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_main.py +13 -8
  13. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_temp.py +3 -3
  14. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_time.py +18 -14
  15. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/file_naming.py +25 -12
  16. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/globmatch.py +2 -2
  17. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/gpg.py +18 -9
  18. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/lazy.py +2 -2
  19. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/librsync.py +4 -4
  20. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/manifest.py +4 -4
  21. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/patchdir.py +18 -12
  22. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/path.py +21 -17
  23. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/selection.py +8 -8
  24. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/statistics.py +3 -3
  25. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12/duplicity.egg-info}/PKG-INFO +1 -1
  26. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/man/duplicity.1 +1 -1
  27. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/pyproject.toml +2 -19
  28. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/setup.py +1 -1
  29. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/AUTHORS.md +0 -0
  30. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/COPYING +0 -0
  31. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-LOG.md +0 -0
  32. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-REPO.md +0 -0
  33. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README-TESTING.md +0 -0
  34. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/README.md +0 -0
  35. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/__main__.py +0 -0
  36. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/_librsyncmodule.c +0 -0
  37. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/argparse311.py +0 -0
  38. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backend_pool.py +0 -0
  39. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/__init__.py +0 -0
  40. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_cf_cloudfiles.py +0 -0
  41. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_cf_pyrax.py +0 -0
  42. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/_testbackend.py +0 -0
  43. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/adbackend.py +0 -0
  44. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/azurebackend.py +0 -0
  45. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/b2backend.py +0 -0
  46. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/boxbackend.py +0 -0
  47. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/cfbackend.py +0 -0
  48. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/gdrivebackend.py +0 -0
  49. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/giobackend.py +0 -0
  50. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/hsibackend.py +0 -0
  51. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/hubicbackend.py +0 -0
  52. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/idrivedbackend.py +0 -0
  53. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/imapbackend.py +0 -0
  54. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/jottacloudbackend.py +0 -0
  55. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/lftpbackend.py +0 -0
  56. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/localbackend.py +0 -0
  57. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/mediafirebackend.py +0 -0
  58. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megabackend.py +0 -0
  59. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megav2backend.py +0 -0
  60. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/megav3backend.py +0 -0
  61. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/multibackend.py +0 -0
  62. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ncftpbackend.py +0 -0
  63. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/onedrivebackend.py +0 -0
  64. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/par2backend.py +0 -0
  65. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pcabackend.py +0 -0
  66. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pydrivebackend.py +0 -0
  67. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pyrax_identity/__init__.py +0 -0
  68. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/pyrax_identity/hubic.py +0 -0
  69. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/rclonebackend.py +0 -0
  70. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/rsyncbackend.py +0 -0
  71. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/s3_boto3_backend.py +0 -0
  72. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/slatebackend.py +0 -0
  73. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ssh_paramiko_backend.py +0 -0
  74. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/ssh_pexpect_backend.py +0 -0
  75. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/swiftbackend.py +0 -0
  76. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/sxbackend.py +0 -0
  77. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/backends/tahoebackend.py +0 -0
  78. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cached_ops.py +0 -0
  79. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_data.py +0 -0
  80. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/cli_util.py +0 -0
  81. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/config.py +0 -0
  82. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/dup_tarfile.py +0 -0
  83. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/errors.py +0 -0
  84. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/filechunkio.py +0 -0
  85. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/gpginterface.py +0 -0
  86. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/log.py +0 -0
  87. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/progress.py +0 -0
  88. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/robust.py +0 -0
  89. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/tempdir.py +0 -0
  90. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity/util.py +0 -0
  91. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/SOURCES.txt +0 -0
  92. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/dependency_links.txt +0 -0
  93. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/entry_points.txt +0 -0
  94. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/requires.txt +0 -0
  95. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/duplicity.egg-info/top_level.txt +0 -0
  96. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/af_ZA/duplicity.mo +0 -0
  97. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ar_SA/duplicity.mo +0 -0
  98. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ca_ES/duplicity.mo +0 -0
  99. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/cs_CZ/duplicity.mo +0 -0
  100. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/da_DK/duplicity.mo +0 -0
  101. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/de_AT/duplicity.mo +0 -0
  102. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/de_DE/duplicity.mo +0 -0
  103. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/el_GR/duplicity.mo +0 -0
  104. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_AU/duplicity.mo +0 -0
  105. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_GB/duplicity.mo +0 -0
  106. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_PR/duplicity.mo +0 -0
  107. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/en_US/duplicity.mo +0 -0
  108. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_EM/duplicity.mo +0 -0
  109. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_ES/duplicity.mo +0 -0
  110. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_MX/duplicity.mo +0 -0
  111. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_PR/duplicity.mo +0 -0
  112. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/es_US/duplicity.mo +0 -0
  113. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/fi_FI/duplicity.mo +0 -0
  114. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/fr_FR/duplicity.mo +0 -0
  115. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/he_IL/duplicity.mo +0 -0
  116. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/hu_HU/duplicity.mo +0 -0
  117. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/it_IT/duplicity.mo +0 -0
  118. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ja_JP/duplicity.mo +0 -0
  119. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ko_KR/duplicity.mo +0 -0
  120. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_BE/duplicity.mo +0 -0
  121. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_NL/duplicity.mo +0 -0
  122. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/nl_SR/duplicity.mo +0 -0
  123. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/no_NO/duplicity.mo +0 -0
  124. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pl_PL/duplicity.mo +0 -0
  125. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pt_BR/duplicity.mo +0 -0
  126. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/pt_PT/duplicity.mo +0 -0
  127. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ro_RO/duplicity.mo +0 -0
  128. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_BY/duplicity.mo +0 -0
  129. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_MD/duplicity.mo +0 -0
  130. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_RU/duplicity.mo +0 -0
  131. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/ru_UA/duplicity.mo +0 -0
  132. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/sr_SP/duplicity.mo +0 -0
  133. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/sv_SE/duplicity.mo +0 -0
  134. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/tr_TR/duplicity.mo +0 -0
  135. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/uk_UA/duplicity.mo +0 -0
  136. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/vi_VN/duplicity.mo +0 -0
  137. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_CN/duplicity.mo +0 -0
  138. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_HK/duplicity.mo +0 -0
  139. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_MO/duplicity.mo +0 -0
  140. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_SG/duplicity.mo +0 -0
  141. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/po/zh_TW/duplicity.mo +0 -0
  142. {duplicity-3.0.6.dev11 → duplicity-3.0.6.dev12}/requirements.txt +0 -0
  143. {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
- ## rel.3.0.6.dev11 (2025-10-16)
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]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duplicity
3
- Version: 3.0.6.dev11
3
+ Version: 3.0.6.dev12
4
4
  Summary: Encrypted backup using rsync algorithm
5
5
  Author-email: Kenneth Loafman <kenneth@loafman.com>
6
6
  Maintainer: Edgar Soldin, Thomas Laubrock
@@ -21,7 +21,7 @@
21
21
 
22
22
  import gettext
23
23
 
24
- __version__: str = "3.0.6.dev11"
25
- __reldate__: str = "October 19, 2025"
24
+ __version__: str = "3.0.6.dev12"
25
+ __reldate__: str = "October 25, 2025"
26
26
 
27
27
  gettext.install("duplicity", names=["ngettext"])
@@ -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(filename_list, bytes)
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 pr.volume_number is not None
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(fullset, BackupSet)
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(filename, pr)
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 self.values_set
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, col_stats.all_backup_chains
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 config.keep_chains is not None
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" # Why write a file and then close it immediately?
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 len(tzd) == 6 # only accept forms like +08:00 for now
243
- assert (tzd[0] == "-" or tzd[0] == "+") and tzd[3] == config.time_separator
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 (volume_number and part_string)
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 (volume_number and part_string)
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: