comicbox 5.2.0__tar.gz → 5.2.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 (593) hide show
  1. {comicbox-5.2.0 → comicbox-5.2.1}/NEWS.md +6 -0
  2. {comicbox-5.2.0 → comicbox-5.2.1}/PKG-INFO +1 -1
  3. {comicbox-5.2.0 → comicbox-5.2.1}/bun.lock +9 -9
  4. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicvine_api/online_source.py +70 -0
  5. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/online_session.py +20 -8
  6. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/run.py +14 -5
  7. {comicbox-5.2.0 → comicbox-5.2.1}/docs/NEWS.md +6 -0
  8. {comicbox-5.2.0 → comicbox-5.2.1}/package.json +1 -1
  9. {comicbox-5.2.0 → comicbox-5.2.1}/pyproject.toml +1 -1
  10. {comicbox-5.2.0 → comicbox-5.2.1}/pyproject.toml.orig +1 -1
  11. comicbox-5.2.1/tests/unit/test_comicvine_session_lifecycle.py +273 -0
  12. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/online_client.py +14 -15
  13. {comicbox-5.2.0 → comicbox-5.2.1}/uv.lock +1 -1
  14. {comicbox-5.2.0 → comicbox-5.2.1}/.dockerignore +0 -0
  15. {comicbox-5.2.0 → comicbox-5.2.1}/.gitignore +0 -0
  16. {comicbox-5.2.0 → comicbox-5.2.1}/.prettierignore +0 -0
  17. {comicbox-5.2.0 → comicbox-5.2.1}/.readthedocs.yaml +0 -0
  18. {comicbox-5.2.0 → comicbox-5.2.1}/.remarkignore +0 -0
  19. {comicbox-5.2.0 → comicbox-5.2.1}/.shellcheckrc +0 -0
  20. {comicbox-5.2.0 → comicbox-5.2.1}/Makefile +0 -0
  21. {comicbox-5.2.0 → comicbox-5.2.1}/README.md +0 -0
  22. {comicbox-5.2.0 → comicbox-5.2.1}/bin/ci-download-dist-if-identical.sh +0 -0
  23. {comicbox-5.2.0 → comicbox-5.2.1}/bin/clean-pycache.sh +0 -0
  24. {comicbox-5.2.0 → comicbox-5.2.1}/bin/cleanup-pypi-alpha.sh +0 -0
  25. {comicbox-5.2.0 → comicbox-5.2.1}/bin/dev-module.sh +0 -0
  26. {comicbox-5.2.0 → comicbox-5.2.1}/bin/docker-compose-exit.sh +0 -0
  27. {comicbox-5.2.0 → comicbox-5.2.1}/bin/docker-tag-latest.sh +0 -0
  28. {comicbox-5.2.0 → comicbox-5.2.1}/bin/fix-docker.sh +0 -0
  29. {comicbox-5.2.0 → comicbox-5.2.1}/bin/fix-python.sh +0 -0
  30. {comicbox-5.2.0 → comicbox-5.2.1}/bin/fix-sh.sh +0 -0
  31. {comicbox-5.2.0 → comicbox-5.2.1}/bin/fix.sh +0 -0
  32. {comicbox-5.2.0 → comicbox-5.2.1}/bin/kill-eslint_d.sh +0 -0
  33. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-ci.sh +0 -0
  34. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-complexity.sh +0 -0
  35. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-darwin.sh +0 -0
  36. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-docker.sh +0 -0
  37. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-python.sh +0 -0
  38. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint-sh.sh +0 -0
  39. {comicbox-5.2.0 → comicbox-5.2.1}/bin/lint.sh +0 -0
  40. {comicbox-5.2.0 → comicbox-5.2.1}/bin/prettier-nginx.sh +0 -0
  41. {comicbox-5.2.0 → comicbox-5.2.1}/bin/publish-pypi.sh +0 -0
  42. {comicbox-5.2.0 → comicbox-5.2.1}/bin/regenerate-pdf-test-pages.py +0 -0
  43. {comicbox-5.2.0 → comicbox-5.2.1}/bin/release-tag.sh +0 -0
  44. {comicbox-5.2.0 → comicbox-5.2.1}/bin/remark-for-claude.sh +0 -0
  45. {comicbox-5.2.0 → comicbox-5.2.1}/bin/roman.py +0 -0
  46. {comicbox-5.2.0 → comicbox-5.2.1}/bin/sort-ignore.sh +0 -0
  47. {comicbox-5.2.0 → comicbox-5.2.1}/bin/test-docker.sh +0 -0
  48. {comicbox-5.2.0 → comicbox-5.2.1}/bin/test-python.sh +0 -0
  49. {comicbox-5.2.0 → comicbox-5.2.1}/bin/type-python.sh +0 -0
  50. {comicbox-5.2.0 → comicbox-5.2.1}/bin/uml.sh +0 -0
  51. {comicbox-5.2.0 → comicbox-5.2.1}/bin/update-deps-node.sh +0 -0
  52. {comicbox-5.2.0 → comicbox-5.2.1}/bin/update-deps-python.sh +0 -0
  53. {comicbox-5.2.0 → comicbox-5.2.1}/bin/version-node.sh +0 -0
  54. {comicbox-5.2.0 → comicbox-5.2.1}/bin/version-python.sh +0 -0
  55. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/ci.mk +0 -0
  56. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/common.mk +0 -0
  57. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/docker.mk +0 -0
  58. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/docs.mk +0 -0
  59. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/eslint.config.base.js +0 -0
  60. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/gha_std.mk +0 -0
  61. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/help.mk +0 -0
  62. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/node.mk +0 -0
  63. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/node_root.mk +0 -0
  64. {comicbox-5.2.0 → comicbox-5.2.1}/cfg/python.mk +0 -0
  65. {comicbox-5.2.0 → comicbox-5.2.1}/check-jsonschema.sh +0 -0
  66. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/__init__.py +0 -0
  67. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/_pdf.py +0 -0
  68. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/_rar.py +0 -0
  69. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/__init__.py +0 -0
  70. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/__init__.py +0 -0
  71. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/archive.py +0 -0
  72. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/archiveinfo.py +0 -0
  73. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/filenames.py +0 -0
  74. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/init.py +0 -0
  75. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/mtime.py +0 -0
  76. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/pages.py +0 -0
  77. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/read.py +0 -0
  78. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/sniff.py +0 -0
  79. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/archive/write.py +0 -0
  80. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/__init__.py +0 -0
  81. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/date.py +0 -0
  82. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/identifiers.py +0 -0
  83. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/issue.py +0 -0
  84. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/notes.py +0 -0
  85. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/pages.py +0 -0
  86. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/stamp.py +0 -0
  87. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/stories_title.py +0 -0
  88. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/url_identifiers.py +0 -0
  89. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/computed/urls.py +0 -0
  90. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/dump.py +0 -0
  91. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/dump_files.py +0 -0
  92. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/init.py +0 -0
  93. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/load.py +0 -0
  94. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/merge.py +0 -0
  95. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/metadata.py +0 -0
  96. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/normalize.py +0 -0
  97. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/online_covers.py +0 -0
  98. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/online_lookup.py +0 -0
  99. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/pages/__init__.py +0 -0
  100. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/pages/covers.py +0 -0
  101. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/pages/extract.py +0 -0
  102. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/print.py +0 -0
  103. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/sources.py +0 -0
  104. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/types.py +0 -0
  105. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/validate/__init__.py +0 -0
  106. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/box/validate/guess_format.py +0 -0
  107. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/cli/__init__.py +0 -0
  108. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/cli/__main__.py +0 -0
  109. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/cli/epilog.py +0 -0
  110. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/cli/parser.py +0 -0
  111. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/__init__.py +0 -0
  112. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/computed.py +0 -0
  113. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/formats.py +0 -0
  114. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/online/__init__.py +0 -0
  115. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/online/build.py +0 -0
  116. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/online/settings.py +0 -0
  117. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/online/template.py +0 -0
  118. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/paths.py +0 -0
  119. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/read.py +0 -0
  120. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config/settings.py +0 -0
  121. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/config_default.yaml +0 -0
  122. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/constants.py +0 -0
  123. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/empty.py +0 -0
  124. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/__init__.py +0 -0
  125. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/comet.py +0 -0
  126. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/comicbookinfo.py +0 -0
  127. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/comicbox.py +0 -0
  128. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/comicinfo.py +0 -0
  129. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/generic/__init__.py +0 -0
  130. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/generic/age_rating.py +0 -0
  131. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/generic/role.py +0 -0
  132. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/__init__.py +0 -0
  133. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/age_rating.py +0 -0
  134. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/formats.py +0 -0
  135. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/identifiers.py +0 -0
  136. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/reading_direction.py +0 -0
  137. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/maps/roles.py +0 -0
  138. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/enums/metroninfo.py +0 -0
  139. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/events.py +0 -0
  140. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/exceptions.py +0 -0
  141. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/__init__.py +0 -0
  142. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/_base.py +0 -0
  143. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/__init__.py +0 -0
  144. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/__init__.py +0 -0
  145. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/collection_fields.py +0 -0
  146. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/comicbox.py +0 -0
  147. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/comicinfo.py +0 -0
  148. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/enum_fields.py +0 -0
  149. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/fields.py +0 -0
  150. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/metroninfo.py +0 -0
  151. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/number_fields.py +0 -0
  152. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/pdf.py +0 -0
  153. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/pycountry.py +0 -0
  154. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/time_fields.py +0 -0
  155. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/union.py +0 -0
  156. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/fields/xml_fields.py +0 -0
  157. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/__init__.py +0 -0
  158. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/auto_engage.py +0 -0
  159. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/cli_overrides.py +0 -0
  160. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/cover_hash.py +0 -0
  161. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/credentials.py +0 -0
  162. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/matcher.py +0 -0
  163. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/outcome_stats.py +0 -0
  164. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/profile.py +0 -0
  165. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/prompt.py +0 -0
  166. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/rate_gate.py +0 -0
  167. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/rate_limits.py +0 -0
  168. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/retry.py +0 -0
  169. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/sanitize.py +0 -0
  170. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/selector.py +0 -0
  171. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/series_cache.py +0 -0
  172. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/series_filter.py +0 -0
  173. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/session_state.py +0 -0
  174. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/signals.py +0 -0
  175. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/sources/__init__.py +0 -0
  176. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/sources/base.py +0 -0
  177. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/transform_helpers.py +0 -0
  178. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/vacuum.py +0 -0
  179. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/online/warn_once.py +0 -0
  180. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/__init__.py +0 -0
  181. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/base.py +0 -0
  182. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/cache.py +0 -0
  183. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/decorators.py +0 -0
  184. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/error_store.py +0 -0
  185. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/json_schemas.py +0 -0
  186. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/xml_schemas.py +0 -0
  187. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/schemas/yaml.py +0 -0
  188. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/__init__.py +0 -0
  189. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/base.py +0 -0
  190. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/identifiers.py +0 -0
  191. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/price.py +0 -0
  192. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/publishing_tags.py +0 -0
  193. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/spec.py +0 -0
  194. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/xml_credits.py +0 -0
  195. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/base/transforms/xml_reprints.py +0 -0
  196. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comet/__init__.py +0 -0
  197. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comet/schema.py +0 -0
  198. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comet/transform/__init__.py +0 -0
  199. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comet/transform/reprints.py +0 -0
  200. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_book_info/__init__.py +0 -0
  201. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_book_info/schema.py +0 -0
  202. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_book_info/transform/__init__.py +0 -0
  203. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_book_info/transform/credits.py +0 -0
  204. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_book_info/transform/rating.py +0 -0
  205. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/__init__.py +0 -0
  206. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/schema.py +0 -0
  207. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/transform/__init__.py +0 -0
  208. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/transform/identifiers.py +0 -0
  209. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/transform/manga.py +0 -0
  210. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/transform/pages.py +0 -0
  211. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comic_info/transform/storyarcs.py +0 -0
  212. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/__init__.py +0 -0
  213. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/README.md +0 -0
  214. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/__init__.py +0 -0
  215. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/cli.py +0 -0
  216. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/identifiers.py +0 -0
  217. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/json_schema.py +0 -0
  218. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/pages.py +0 -0
  219. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/publishing.py +0 -0
  220. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/schema/yaml.py +0 -0
  221. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/__init__.py +0 -0
  222. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/cli.py +0 -0
  223. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/credits.py +0 -0
  224. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/json.py +0 -0
  225. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/name_objs.py +0 -0
  226. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicbox/transform/yaml.py +0 -0
  227. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicvine_api/__init__.py +0 -0
  228. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicvine_api/schema.py +0 -0
  229. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/comicvine_api/transform.py +0 -0
  230. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/filename/__init__.py +0 -0
  231. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/filename/schema.py +0 -0
  232. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/filename/transform.py +0 -0
  233. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_api/__init__.py +0 -0
  234. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_api/online_source.py +0 -0
  235. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_api/paced_session.py +0 -0
  236. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_api/schema.py +0 -0
  237. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_api/transform.py +0 -0
  238. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/__init__.py +0 -0
  239. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/__init__.py +0 -0
  240. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/credits.py +0 -0
  241. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/identifiers.py +0 -0
  242. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/price.py +0 -0
  243. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/publishing.py +0 -0
  244. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/rating.py +0 -0
  245. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/schema/resource.py +0 -0
  246. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/__init__.py +0 -0
  247. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/const.py +0 -0
  248. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/credits.py +0 -0
  249. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/identified_name.py +0 -0
  250. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/identifier_attribute.py +0 -0
  251. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/identifiers.py +0 -0
  252. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/publishing_tags.py +0 -0
  253. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/rating.py +0 -0
  254. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/reprints.py +0 -0
  255. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/metron_info/transform/resources.py +0 -0
  256. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/pdf/__init__.py +0 -0
  257. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/pdf/schema.py +0 -0
  258. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/pdf/transform/__init__.py +0 -0
  259. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/pdf/transform/credits.py +0 -0
  260. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/formats/sources.py +0 -0
  261. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/identifiers/__init__.py +0 -0
  262. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/identifiers/identifiers.py +0 -0
  263. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/identifiers/other.py +0 -0
  264. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/identifiers/urns.py +0 -0
  265. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/logger.py +0 -0
  266. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/merge/__init__.py +0 -0
  267. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/merge/mergedeep.py +0 -0
  268. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/online_estimate.py +0 -0
  269. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/predict.py +0 -0
  270. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/print.py +0 -0
  271. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/process.py +0 -0
  272. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/py.typed +0 -0
  273. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/CoMet-v1.1.xsd +0 -0
  274. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/ComicInfo-v2.1-Draft.xsd +0 -0
  275. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/MetronInfo-v1.1.xsd +0 -0
  276. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/comic-book-info-v1.0.schema.json +0 -0
  277. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v1.0/comicbox-v1.0.schema.json +0 -0
  278. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v1.0/page.schema.json +0 -0
  279. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v1.0/reprint.schema.json +0 -0
  280. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v1.0/series.schema.json +0 -0
  281. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v1.0/volume.schema.json +0 -0
  282. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/comicbox-v2.0.schema.json +0 -0
  283. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/comicbox.example.yaml +0 -0
  284. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/credit.schema.json +0 -0
  285. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/date.schema.json +0 -0
  286. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/identified-object.schema.json +0 -0
  287. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/identified-objects.schema.json +0 -0
  288. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/identifiers.schema.json +0 -0
  289. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/named-identified-object.schema.json +0 -0
  290. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/page.schema.json +0 -0
  291. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/reprint.schema.json +0 -0
  292. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/series.schema.json +0 -0
  293. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v2.0/volume.schema.json +0 -0
  294. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/alternative-name.schema.json +0 -0
  295. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/comicbox-v3.0.schema.json +0 -0
  296. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/comicbox.example.yaml +0 -0
  297. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/credit.schema.json +0 -0
  298. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/date.schema.json +0 -0
  299. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/identified-object.schema.json +0 -0
  300. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/identified-objects.schema.json +0 -0
  301. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/identifiers.schema.json +0 -0
  302. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/named-identified-object.schema.json +0 -0
  303. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/page.schema.json +0 -0
  304. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/reprint.schema.json +0 -0
  305. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/role.schema.json +0 -0
  306. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/series.schema.json +0 -0
  307. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/schemas/v3.0/volume.schema.json +0 -0
  308. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/__init__.py +0 -0
  309. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/base.py +0 -0
  310. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/json_validator.py +0 -0
  311. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/spec.py +0 -0
  312. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/xml_validator.py +0 -0
  313. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/validate/yaml_validator.py +0 -0
  314. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/version.py +0 -0
  315. {comicbox-5.2.0 → comicbox-5.2.1}/comicbox/write.py +0 -0
  316. {comicbox-5.2.0 → comicbox-5.2.1}/compose.yaml +0 -0
  317. {comicbox-5.2.0 → comicbox-5.2.1}/docs/README.md +0 -0
  318. {comicbox-5.2.0 → comicbox-5.2.1}/docs/requirements.txt +0 -0
  319. {comicbox-5.2.0 → comicbox-5.2.1}/docs/style.css +0 -0
  320. {comicbox-5.2.0 → comicbox-5.2.1}/docs/style.mkdocs.css +0 -0
  321. {comicbox-5.2.0 → comicbox-5.2.1}/eslint.config.js +0 -0
  322. {comicbox-5.2.0 → comicbox-5.2.1}/generate-schema/gen-schema.sh +0 -0
  323. {comicbox-5.2.0 → comicbox-5.2.1}/generate-schema/mjs.py +0 -0
  324. {comicbox-5.2.0 → comicbox-5.2.1}/mkdocs.yml +0 -0
  325. {comicbox-5.2.0 → comicbox-5.2.1}/tests/__init__.py +0 -0
  326. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/.gitignore +0 -0
  327. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/README.md +0 -0
  328. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/__init__.py +0 -0
  329. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/_harness_helpers.py +0 -0
  330. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/bootstrap.py +0 -0
  331. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/compare.py +0 -0
  332. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/debug_search.py +0 -0
  333. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/fixtures.example.json +0 -0
  334. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/label_metron.py +0 -0
  335. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/run.py +0 -0
  336. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/sample.py +0 -0
  337. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/summarize.py +0 -0
  338. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_bootstrap.py +0 -0
  339. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_compare.py +0 -0
  340. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_harness.py +0 -0
  341. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_harness_checkpoint.py +0 -0
  342. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_harness_diagnostics.py +0 -0
  343. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_harness_filter.py +0 -0
  344. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_harness_resume.py +0 -0
  345. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_label_metron.py +0 -0
  346. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_sample.py +0 -0
  347. {comicbox-5.2.0 → comicbox-5.2.1}/tests/calibration/test_summarize.py +0 -0
  348. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/__init__.py +0 -0
  349. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_exit_code.py +0 -0
  350. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_export.py +0 -0
  351. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_extract.py +0 -0
  352. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_online.py +0 -0
  353. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_print.py +0 -0
  354. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_rename.py +0 -0
  355. {comicbox-5.2.0 → comicbox-5.2.1}/tests/cli/test_cli_write.py +0 -0
  356. {comicbox-5.2.0 → comicbox-5.2.1}/tests/conftest.py +0 -0
  357. {comicbox-5.2.0 → comicbox-5.2.1}/tests/const.py +0 -0
  358. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001 (1950) The Beginning - multi.cbz +0 -0
  359. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001 (1950) The Beginning - nothing.cbz +0 -0
  360. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-cbi.cbr +0 -0
  361. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-cix-cbi.cbr +0 -0
  362. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-cix.cbt +0 -0
  363. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-cix.cbz +0 -0
  364. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-comet.cbz +0 -0
  365. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001-metron.cbz +0 -0
  366. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001.cb7 +0 -0
  367. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science #001.cbz +0 -0
  368. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CaptainScience#1_01.jpg +0 -0
  369. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CaptainScience#1_02.jpg +0 -0
  370. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CaptainScience#1_03.jpg +0 -0
  371. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CaptainScience#1_04.jpg +0 -0
  372. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CaptainScience#1_05.jpg +0 -0
  373. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/CoMet.xml +0 -0
  374. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/comic-book-info.json +0 -0
  375. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/comicinfo-original.xml +0 -0
  376. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/comicinfo.xml +0 -0
  377. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/metroninfo-full.xml +0 -0
  378. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/Captain Science 001/metroninfo.xml +0 -0
  379. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/comicbox-cli.cbz +0 -0
  380. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/comicbox.cbz +0 -0
  381. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/create-archive.sh +0 -0
  382. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/create_cs_archives.py +0 -0
  383. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/create_macos_resource_fork.py +0 -0
  384. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/empty.cbz +0 -0
  385. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/examples/comic-book-info-example.json +0 -0
  386. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/examples/metroninfo-v1.1-valid.xml +0 -0
  387. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comet.xml +0 -0
  388. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comic-book-info.json +0 -0
  389. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comicbox-cli.yaml +0 -0
  390. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comicbox.json +0 -0
  391. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comicbox.yaml +0 -0
  392. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/comicinfo.xml +0 -0
  393. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/create-export-archive.sh +0 -0
  394. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/metroninfo.xml +0 -0
  395. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/mupdf.json +0 -0
  396. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export/pdf-metadata.xml +0 -0
  397. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/export.cbz +0 -0
  398. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/list_recurse_output.txt +0 -0
  399. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/macos_resource_fork.cbz +0 -0
  400. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comet-cover-path-list.xml +0 -0
  401. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comet-write.xml +0 -0
  402. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comet.xml +0 -0
  403. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comic-book-info-example.json +0 -0
  404. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comic-book-info.json +0 -0
  405. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox-cli.yaml +0 -0
  406. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox-filename.txt +0 -0
  407. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox-write.json +0 -0
  408. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox-write.yaml +0 -0
  409. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox.json +0 -0
  410. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox.xml +0 -0
  411. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicbox.yaml +0 -0
  412. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-bad-multiple.xml +0 -0
  413. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-cover-path-list.xml +0 -0
  414. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-ids-from-tags.xml +0 -0
  415. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-metron-origin.xml +0 -0
  416. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-notes-date.xml +0 -0
  417. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-validation.xml +0 -0
  418. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo-write.xml +0 -0
  419. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/comicinfo.xml +0 -0
  420. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/create-archives.sh +0 -0
  421. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/metroninfo-no-primary-id.xml +0 -0
  422. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/metroninfo-v1.1-valid.xml +0 -0
  423. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/metroninfo-write.xml +0 -0
  424. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/metroninfo.xml +0 -0
  425. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/mupdf-nested.json +0 -0
  426. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/mupdf.json +0 -0
  427. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/pdf-cix.xml +0 -0
  428. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/pdf-nested.xml +0 -0
  429. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/metadata/pdf.xml +0 -0
  430. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comet.xml +0 -0
  431. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comic-book-info.json +0 -0
  432. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comicbox-filename.txt +0 -0
  433. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comicbox.json +0 -0
  434. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comicbox.yaml +0 -0
  435. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/comicinfo.xml +0 -0
  436. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/multi/create-multi-archive.sh +0 -0
  437. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/patchme.cbz +0 -0
  438. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/pdf/0.pdf +0 -0
  439. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/pdf/1.pdf +0 -0
  440. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/pdf/2.pdf +0 -0
  441. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/pdf/3.pdf +0 -0
  442. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/test_cix.pdf +0 -0
  443. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/test_pdf.pdf +0 -0
  444. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/test_pdf_legacy_keywords.pdf +0 -0
  445. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/xml.cbz +0 -0
  446. {comicbox-5.2.0 → comicbox-5.2.1}/tests/files/yaml.cbz +0 -0
  447. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/__init__.py +0 -0
  448. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_cbi.py +0 -0
  449. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_cix.py +0 -0
  450. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_comet.py +0 -0
  451. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_filename.py +0 -0
  452. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_json.py +0 -0
  453. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_json_schema_drift.py +0 -0
  454. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_metron.py +0 -0
  455. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_multi.py +0 -0
  456. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_pdf_json.py +0 -0
  457. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_pdf_xml.py +0 -0
  458. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_validation.py +0 -0
  459. {comicbox-5.2.0 → comicbox-5.2.1}/tests/schemas/test_yaml.py +0 -0
  460. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/.gitignore +0 -0
  461. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/README.md +0 -0
  462. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/__init__.py +0 -0
  463. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/jobs_accuracy.py +0 -0
  464. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/prompt_ux.py +0 -0
  465. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/run.py +0 -0
  466. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/test_jobs_accuracy.py +0 -0
  467. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/test_prompt_ux.py +0 -0
  468. {comicbox-5.2.0 → comicbox-5.2.1}/tests/stress/test_run.py +0 -0
  469. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_codex_api.py +0 -0
  470. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_compute.py +0 -0
  471. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_convert_pdf.py +0 -0
  472. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_extract.py +0 -0
  473. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_import_export.py +0 -0
  474. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_mtime.py +0 -0
  475. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_pages.py +0 -0
  476. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_stories_title.py +0 -0
  477. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_validate_testfiles.py +0 -0
  478. {comicbox-5.2.0 → comicbox-5.2.1}/tests/test_writing.py +0 -0
  479. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/__init__.py +0 -0
  480. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_archive_errors.py +0 -0
  481. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_archive_read.py +0 -0
  482. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_archiveinfo_mtime.py +0 -0
  483. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_auto_engage.py +0 -0
  484. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_box_cache_invalidation.py +0 -0
  485. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_cbi_rating_normalize.py +0 -0
  486. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_cli_choices_enum_drift.py +0 -0
  487. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_cli_dest_folds.py +0 -0
  488. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_cli_dest_template_drift.py +0 -0
  489. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_comicinfo_alternates.py +0 -0
  490. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_comicinfo_series_groups.py +0 -0
  491. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_comicvine.py +0 -0
  492. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_comicvine_client.py +0 -0
  493. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_computed_pipeline.py +0 -0
  494. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_computed_reprints.py +0 -0
  495. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_container_inputs.py +0 -0
  496. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_defaults_drift.py +0 -0
  497. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_env_tree.py +0 -0
  498. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_file_option.py +0 -0
  499. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_layering.py +0 -0
  500. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_no_path_guard.py +0 -0
  501. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_online_leaf.py +0 -0
  502. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_config_online_sources.py +0 -0
  503. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_cover_hash.py +0 -0
  504. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_credit_primary.py +0 -0
  505. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_defer_prompts.py +0 -0
  506. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_destination_occupied_error.py +0 -0
  507. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_error_store_visibility.py +0 -0
  508. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_exceptions.py +0 -0
  509. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_identifiers.py +0 -0
  510. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_lazy_startup.py +0 -0
  511. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_library_logging.py +0 -0
  512. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_list_field_dedupe.py +0 -0
  513. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_list_field_sort_key_types.py +0 -0
  514. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_manga_and_age_rating.py +0 -0
  515. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_manga_volume.py +0 -0
  516. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_merge_mode_config.py +0 -0
  517. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_merge_order.py +0 -0
  518. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_merge_precedence.py +0 -0
  519. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_mergedeep.py +0 -0
  520. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_community_rating.py +0 -0
  521. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_id_attribute_fallback.py +0 -0
  522. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_prefetch.py +0 -0
  523. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_session_lifecycle.py +0 -0
  524. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_source.py +0 -0
  525. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_metron_transform.py +0 -0
  526. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online.py +0 -0
  527. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_abort_propagation.py +0 -0
  528. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_degradation.py +0 -0
  529. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_estimate.py +0 -0
  530. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_events.py +0 -0
  531. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_lookup.py +0 -0
  532. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_matcher.py +0 -0
  533. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_matcher_resolution.py +0 -0
  534. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_matcher_scoring.py +0 -0
  535. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_prompt.py +0 -0
  536. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_prompt_loops.py +0 -0
  537. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_result_contract.py +0 -0
  538. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_retry.py +0 -0
  539. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_selector.py +0 -0
  540. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_session.py +0 -0
  541. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_online_session_state.py +0 -0
  542. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_outcome_stats.py +0 -0
  543. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_paced_session.py +0 -0
  544. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_parse_decimal.py +0 -0
  545. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_parse_issue.py +0 -0
  546. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_pdf_pages_help.py +0 -0
  547. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_process.py +0 -0
  548. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_prompt_dedup.py +0 -0
  549. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_quota_reserved_skip.py +0 -0
  550. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rar_time_overflow.py +0 -0
  551. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rate_gate.py +0 -0
  552. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rate_gate_concurrency.py +0 -0
  553. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rate_limits.py +0 -0
  554. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_read_events.py +0 -0
  555. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_refresh_cache.py +0 -0
  556. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rename_and_convert_safety.py +0 -0
  557. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_reprints_and_alternative_names.py +0 -0
  558. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_rich_transforms.py +0 -0
  559. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_role_vocabulary.py +0 -0
  560. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_run_parallel.py +0 -0
  561. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_run_recurse.py +0 -0
  562. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_series_cache.py +0 -0
  563. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_series_filter.py +0 -0
  564. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_series_single_flight.py +0 -0
  565. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_session_series_batching.py +0 -0
  566. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_single_valued_tags.py +0 -0
  567. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_sniff.py +0 -0
  568. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_sources.py +0 -0
  569. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_to_metron_age_rating.py +0 -0
  570. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_transform_spec_dest_collisions.py +0 -0
  571. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_transform_spec_missing_keys.py +0 -0
  572. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_transform_spec_scope_phases.py +0 -0
  573. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_urls_and_identifiers.py +0 -0
  574. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_vacuum.py +0 -0
  575. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_validate.py +0 -0
  576. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_write_api.py +0 -0
  577. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_write_destination_prediction.py +0 -0
  578. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_write_destination_safety.py +0 -0
  579. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_write_order_and_claims.py +0 -0
  580. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_xml_cdata_fields.py +0 -0
  581. {comicbox-5.2.0 → comicbox-5.2.1}/tests/unit/test_yes_no_field.py +0 -0
  582. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/__init__.py +0 -0
  583. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/compare.py +0 -0
  584. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/diff.py +0 -0
  585. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/metadata.py +0 -0
  586. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/metron_transport.py +0 -0
  587. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/online_matcher.py +0 -0
  588. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/online_prompt.py +0 -0
  589. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/parser.py +0 -0
  590. {comicbox-5.2.0 → comicbox-5.2.1}/tests/util/tmp.py +0 -0
  591. {comicbox-5.2.0 → comicbox-5.2.1}/tests/validate_cli.py +0 -0
  592. {comicbox-5.2.0 → comicbox-5.2.1}/validate-schema.sh +0 -0
  593. {comicbox-5.2.0 → comicbox-5.2.1}/vulture_ignorelist.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # 📰 News
2
2
 
3
+ ## v5.2.1
4
+
5
+ - Fixes
6
+ - `OnlineSession.close()` releases Comic Vine's connections and sqlite
7
+ handles too, not just Metron's.
8
+
3
9
  ## v5.2.0
4
10
 
5
11
  - Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comicbox
3
- Version: 5.2.0
3
+ Version: 5.2.1
4
4
  Summary: Comic book archive multi format metadata read/write/transform tool and image extractor.
5
5
  Keywords: cb7,cbr,cbt,cbz,comet,comic,comicbookinfo,comicinfo,metroninfo,pdf
6
6
  Author: AJ Slater
@@ -14,7 +14,7 @@
14
14
  "eslint-config-prettier": "^10.1.8",
15
15
  "eslint-plugin-array-func": "^5.1.1",
16
16
  "eslint-plugin-compat": "^7.0.2",
17
- "eslint-plugin-de-morgan": "^2.1.3",
17
+ "eslint-plugin-de-morgan": "^2.2.0",
18
18
  "eslint-plugin-depend": "^1.5.0",
19
19
  "eslint-plugin-html": "^8.2.0",
20
20
  "eslint-plugin-import-x": "^4.17.1",
@@ -174,19 +174,19 @@
174
174
 
175
175
  "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
176
176
 
177
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.70.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.70.0", "@typescript-eslint/types": "^8.70.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ=="],
177
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.70.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.70.1", "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA=="],
178
178
 
179
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.70.0", "", { "dependencies": { "@typescript-eslint/types": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0" } }, "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ=="],
179
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.70.1", "", { "dependencies": { "@typescript-eslint/types": "8.70.1", "@typescript-eslint/visitor-keys": "8.70.1" } }, "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw=="],
180
180
 
181
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.70.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw=="],
181
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.70.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg=="],
182
182
 
183
- "@typescript-eslint/types": ["@typescript-eslint/types@8.70.0", "", {}, "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ=="],
183
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.70.1", "", {}, "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q=="],
184
184
 
185
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.70.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.70.0", "@typescript-eslint/tsconfig-utils": "8.70.0", "@typescript-eslint/types": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA=="],
185
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.70.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.70.1", "@typescript-eslint/tsconfig-utils": "8.70.1", "@typescript-eslint/types": "8.70.1", "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g=="],
186
186
 
187
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.70.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.70.0", "@typescript-eslint/types": "8.70.0", "@typescript-eslint/typescript-estree": "8.70.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA=="],
187
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.70.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.70.1", "@typescript-eslint/types": "8.70.1", "@typescript-eslint/typescript-estree": "8.70.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw=="],
188
188
 
189
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.70.0", "", { "dependencies": { "@typescript-eslint/types": "8.70.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ=="],
189
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.70.1", "", { "dependencies": { "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw=="],
190
190
 
191
191
  "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="],
192
192
 
@@ -370,7 +370,7 @@
370
370
 
371
371
  "eslint-plugin-compat": ["eslint-plugin-compat@7.0.2", "", { "dependencies": { "@mdn/browser-compat-data": "^6.1.1", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.25.2", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", "semver": "^7.6.2" }, "peerDependencies": { "eslint": "^9.0.0 || ^10.0.0" } }, "sha512-gN8hF+4NzMsHUbr4m/TYZK0FtW3DcV4g8rXpTsY2EV5xiRD8jsilUlB9lNSkGGX0veDCxMhKSWbSd+faJByQDA=="],
372
372
 
373
- "eslint-plugin-de-morgan": ["eslint-plugin-de-morgan@2.1.3", "", { "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-vuU/AIPvzsRhCyAfgaWwRVy0QUGvtv2pCn3N6tV4LZRFRNKPuV+Md1mYLvLuM9tORKLe8plzx5DqS9/II/3dew=="],
373
+ "eslint-plugin-de-morgan": ["eslint-plugin-de-morgan@2.2.0", "", { "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-bHg8e68rQTJ01av4sxhgNWQeoSzPWDjb6MqeKgmgRGaCzq83Ou6/g5S1ZssvFdHHS4BsblkJkagoioUmSEHPMg=="],
374
374
 
375
375
  "eslint-plugin-depend": ["eslint-plugin-depend@1.5.0", "", { "dependencies": { "empathic": "^2.0.0", "module-replacements": "^2.10.1", "semver": "^7.6.3" }, "peerDependencies": { "eslint": ">=8.40.0" } }, "sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A=="],
376
376
 
@@ -126,6 +126,76 @@ def reset_shared_sessions() -> None:
126
126
  _session_cache.clear()
127
127
 
128
128
 
129
+ def close_shared_sessions() -> None:
130
+ """
131
+ Release the connections and sqlite handles every shared client holds.
132
+
133
+ Optional, and not a cancel: a closed client stays usable, at the cost
134
+ of reopening what it needs on its next request. The response cache
135
+ reopens lazily and the hourly budget lives in the rate-limit bucket
136
+ *file* rather than in the bucket object, so nothing a run has spent
137
+ is forgotten by closing. Mirrors `metron_api`'s `close_shared_sessions`.
138
+ """
139
+ with _session_cache_lock:
140
+ entries = list(_session_cache.values())
141
+ for client, _signature in entries:
142
+ close_client(client)
143
+
144
+
145
+ def close_client(client: Any) -> None:
146
+ """
147
+ Close one simyan client: the session first, then what that leaves open.
148
+
149
+ A `Comicvine` exposes no `close` of its own, so this goes through
150
+ `_session` -- private simyan surface, as in `_maintain_cache`.
151
+ Closing it releases the pooled sockets and the response cache's
152
+ sqlite connection and stops pyrate-limiter's leaker thread, but
153
+ leaves the rate-limit buckets holding theirs (`_close_limiter_buckets`).
154
+ """
155
+ # Tests seed fakes that are neither `Comicvine`s nor real sessions.
156
+ session = getattr(client, "_session", client)
157
+ close = getattr(session, "close", None)
158
+ if close is not None:
159
+ close()
160
+ _close_limiter_buckets(session)
161
+
162
+
163
+ def _close_limiter_buckets(session: Any) -> None:
164
+ """
165
+ Close the rate-limit buckets the session's own close leaves open.
166
+
167
+ requests-ratelimiter's `close` does ask pyrate-limiter to close them,
168
+ but `BucketFactory.close()` drops its leaker before iterating
169
+ `get_buckets()`, which reads that leaker -- so the loop sees nothing
170
+ and every `SQLiteBucket` keeps its sqlite connection (pyrate-limiter
171
+ 4.5.0). There is one bucket per endpoint pool, so closing only the
172
+ session trades a silent leak for a `ResourceWarning` apiece once the
173
+ buckets are finally collected: worse than not closing at all.
174
+
175
+ The factory would hand a closed bucket straight back out, and a
176
+ request holding one dies on its `None` connection, so the registry is
177
+ emptied first -- a racing lookup then builds a fresh bucket over the
178
+ same file instead of finding the one being closed. Each bucket's own
179
+ lock covers the rest: `close` waits behind an in-flight acquire.
180
+ """
181
+ factory = getattr(getattr(session, "limiter", None), "bucket_factory", None)
182
+ registry = getattr(factory, "buckets", None)
183
+ if not isinstance(registry, dict):
184
+ # The limiter stack is upstream's to rearrange; a run that cannot
185
+ # find the buckets has still closed the session above.
186
+ logger.debug("online comicvine: no rate-limit buckets to close")
187
+ return
188
+ buckets = list(registry.values())
189
+ registry.clear()
190
+ for bucket in buckets:
191
+ try:
192
+ bucket.close()
193
+ # Broad on purpose: one bucket backend that will not close must
194
+ # not cost the others their close, nor fail the caller's teardown.
195
+ except Exception as exc:
196
+ logger.debug(f"online comicvine: rate-limit bucket close skipped: {exc}")
197
+
198
+
129
199
  def _bucket_window(conn: sqlite3.Connection, table: str, now_ms: int) -> dict[str, Any]:
130
200
  """Summarize one bucket table as a `limit`/`remaining`/`reset_epoch` window."""
131
201
  window_start = now_ms - _RATE_LIMIT_WINDOW_MS
@@ -422,18 +422,30 @@ class OnlineSession:
422
422
 
423
423
  def close(self) -> None:
424
424
  """
425
- Release the pooled Metron HTTP connections.
425
+ Release the pooled connections and cache handles both sources hold.
426
426
 
427
427
  Optional, and not a cancel: the session stays usable afterwards,
428
- at the cost of a reconnect on its next request. The mokkari
429
- session is process-wide per credential set, so closing while
430
- another `OnlineSession` in the same process is mid-run costs that
431
- run a reconnect too. Acceptable, and the alternative -- leaving
432
- the sockets to be finalized -- is what emits `ResourceWarning`.
428
+ at the cost of reopening what it needs on its next request. The
429
+ upstream clients are process-wide per credential set, so closing
430
+ while another `OnlineSession` in the same process is mid-run
431
+ costs that run the same reopen. Acceptable, and the alternative
432
+ -- leaving the sockets and sqlite handles to be finalized -- is
433
+ what emits `ResourceWarning`.
434
+
435
+ Comic Vine is the one sharp edge: its rate-limit buckets are
436
+ closed with the session, so a lookup already holding one can fail
437
+ outright rather than merely reconnect. Close between files, not
438
+ under one.
433
439
  """
434
- from comicbox.formats.metron_api.online_source import close_shared_sessions
440
+ from comicbox.formats.comicvine_api.online_source import (
441
+ close_shared_sessions as close_comicvine,
442
+ )
443
+ from comicbox.formats.metron_api.online_source import (
444
+ close_shared_sessions as close_metron,
445
+ )
435
446
 
436
- close_shared_sessions()
447
+ close_metron()
448
+ close_comicvine()
437
449
 
438
450
  def __enter__(self) -> Self:
439
451
  """Enter the context manager, returning this session."""
@@ -37,6 +37,13 @@ if TYPE_CHECKING:
37
37
  #: log one line. Anything else is a bug and earns the stack.
38
38
  _EXPECTED_FILE_ERRORS = (UnsupportedArchiveTypeError,)
39
39
 
40
+ #: The online sources that hold process-wide clients to release at the
41
+ #: end of a run. Named, not imported: see `_close_shared_online_sessions`.
42
+ _ONLINE_SOURCE_MODULES = (
43
+ "comicbox.formats.metron_api.online_source",
44
+ "comicbox.formats.comicvine_api.online_source",
45
+ )
46
+
40
47
 
41
48
  def _leaders_first(clustered: list[Path]) -> list[Path]:
42
49
  """
@@ -55,15 +62,17 @@ def _leaders_first(clustered: list[Path]) -> list[Path]:
55
62
 
56
63
  def _close_shared_online_sessions() -> None:
57
64
  """
58
- Release the pooled Metron connections a run opened, if it opened any.
65
+ Release the connections and handles a run opened, if it opened any.
59
66
 
60
67
  Read out of ``sys.modules`` rather than imported: an offline run
61
68
  never loads the online packages, and importing one here just to call
62
- a no-op would put mokkari's import cost back on every run.
69
+ a no-op would put mokkari's and simyan's import cost back on every
70
+ run.
63
71
  """
64
- metron = sys.modules.get("comicbox.formats.metron_api.online_source")
65
- if metron is not None:
66
- metron.close_shared_sessions()
72
+ for name in _ONLINE_SOURCE_MODULES:
73
+ module = sys.modules.get(name)
74
+ if module is not None:
75
+ module.close_shared_sessions()
67
76
 
68
77
 
69
78
  class Runner:
@@ -1,5 +1,11 @@
1
1
  # 📰 News
2
2
 
3
+ ## v5.2.1
4
+
5
+ - Fixes
6
+ - `OnlineSession.close()` releases Comic Vine's connections and sqlite
7
+ handles too, not just Metron's.
8
+
3
9
  ## v5.2.0
4
10
 
5
11
  - Features
@@ -77,7 +77,7 @@
77
77
  "eslint-config-prettier": "^10.1.8",
78
78
  "eslint-plugin-array-func": "^5.1.1",
79
79
  "eslint-plugin-compat": "^7.0.2",
80
- "eslint-plugin-de-morgan": "^2.1.3",
80
+ "eslint-plugin-de-morgan": "^2.2.0",
81
81
  "eslint-plugin-depend": "^1.5.0",
82
82
  "eslint-plugin-html": "^8.2.0",
83
83
  "eslint-plugin-import-x": "^4.17.1",
@@ -56,7 +56,7 @@ readme = "README.md"
56
56
  requires-python = ">=3.10"
57
57
  license = "LGPL-3.0-only"
58
58
  name = "comicbox"
59
- version = "5.2.0"
59
+ version = "5.2.1"
60
60
 
61
61
  [[project.authors]]
62
62
  name = "AJ Slater"
@@ -56,7 +56,7 @@ readme = "README.md"
56
56
  requires-python = ">=3.10"
57
57
  license = "LGPL-3.0-only"
58
58
  name = "comicbox"
59
- version = "5.2.0"
59
+ version = "5.2.1"
60
60
  [[project.authors]]
61
61
  name = "AJ Slater"
62
62
  email = "aj@slater.net"
@@ -0,0 +1,273 @@
1
+ """
2
+ Lifecycle of the shared simyan client.
3
+
4
+ A `Comicvine` holds more than sockets: the response cache's sqlite
5
+ connection, one rate-limit bucket connection per endpoint pool, and
6
+ pyrate-limiter's leaker thread. Closing the session releases the first
7
+ and stops the last but leaves every bucket open -- and, by dropping the
8
+ references that had kept them alive, turns a silent leak into a
9
+ `ResourceWarning` apiece. These tests pin the whole close.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import gc
15
+ import warnings
16
+ from argparse import Namespace
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ import pytest
20
+ from typing_extensions import override
21
+
22
+ from comicbox.config.online.settings import OnlineCacheSettings, OnlineSettings
23
+ from comicbox.formats.base.online.rate_limits import COMICVINE_DEFAULT_PER_HOUR
24
+ from comicbox.formats.comicvine_api import online_source as comicvine
25
+ from tests.util.online_client import build_comicvine_client, spend
26
+
27
+ if TYPE_CHECKING:
28
+ from pathlib import Path
29
+
30
+
31
+ class _FakeBucket:
32
+ """A bucket double that records the registry's state when it was closed."""
33
+
34
+ def __init__(self) -> None:
35
+ self.closed = 0
36
+ self.registry: dict[str, Any] = {}
37
+ self.registry_was_empty: bool | None = None
38
+
39
+ def close(self) -> None:
40
+ self.closed += 1
41
+ self.registry_was_empty = not self.registry
42
+
43
+
44
+ class _StuckBucket(_FakeBucket):
45
+ """A bucket double whose close fails, as a rearranged upstream might."""
46
+
47
+ @override
48
+ def close(self) -> None:
49
+ super().close()
50
+ msg = "no connection"
51
+ raise RuntimeError(msg)
52
+
53
+
54
+ class _FakeSession:
55
+ """A `CachedLimiterSession` double, down to `limiter.bucket_factory`."""
56
+
57
+ def __init__(self, *buckets: _FakeBucket) -> None:
58
+ self.closed = 0
59
+ registry = {f"pool{i}": bucket for i, bucket in enumerate(buckets)}
60
+ for bucket in buckets:
61
+ bucket.registry = registry
62
+ self.limiter = Namespace(bucket_factory=Namespace(buckets=registry))
63
+
64
+ @property
65
+ def registry(self) -> dict[str, Any]:
66
+ """The bucket registry the factory serves lookups from."""
67
+ return self.limiter.bucket_factory.buckets
68
+
69
+ def close(self) -> None:
70
+ self.closed += 1
71
+
72
+
73
+ class _FakeClient:
74
+ """A `Comicvine` double: the session hangs off the same private name."""
75
+
76
+ def __init__(self, session: _FakeSession) -> None:
77
+ self._session = session
78
+
79
+
80
+ class _Unclosable:
81
+ """A client double with neither a session nor a close, as fakes have."""
82
+
83
+
84
+ @pytest.fixture(autouse=True)
85
+ def _clear_cache(monkeypatch: pytest.MonkeyPatch) -> None:
86
+ """Every test gets its own empty shared-client cache."""
87
+ monkeypatch.setattr(comicvine, "_session_cache", {})
88
+
89
+
90
+ def _seed(client: Any, key: tuple[str, str] = ("key", "")) -> None:
91
+ comicvine._session_cache[key] = (client, ())
92
+
93
+
94
+ def _buckets(client: Any) -> list[Any]:
95
+ return list(client._session.limiter.bucket_factory.buckets.values())
96
+
97
+
98
+ def test_close_closes_every_shared_client() -> None:
99
+ """One run, one close per credential set."""
100
+ first = _FakeSession()
101
+ second = _FakeSession()
102
+ _seed(_FakeClient(first), ("key", ""))
103
+ _seed(_FakeClient(second), ("other", "https://cv.example/api"))
104
+
105
+ comicvine.close_shared_sessions()
106
+
107
+ assert first.closed == 1
108
+ assert second.closed == 1
109
+
110
+
111
+ def test_close_keeps_the_cache_entries() -> None:
112
+ """
113
+ A closed simyan client is still the right one to reuse.
114
+
115
+ It reopens its response cache on demand and rebuilds a rate-limit
116
+ bucket over the same file, so the credential set keeps its client.
117
+ """
118
+ _seed(_FakeClient(_FakeSession()))
119
+
120
+ comicvine.close_shared_sessions()
121
+
122
+ assert comicvine._session_cache
123
+
124
+
125
+ def test_close_tolerates_a_client_without_a_session() -> None:
126
+ """Tests seed doubles; a missing `_session` must not break teardown."""
127
+ _seed(_Unclosable())
128
+
129
+ comicvine.close_shared_sessions() # must not raise
130
+
131
+
132
+ def test_close_empties_the_registry_before_closing_the_buckets() -> None:
133
+ """
134
+ A closed bucket must never be handed back out.
135
+
136
+ The factory serves its registry without checking, and a request that
137
+ gets a closed bucket dies on its `None` connection, so the registry
138
+ is emptied first: a racing lookup then builds a fresh bucket instead
139
+ of finding this one mid-close.
140
+ """
141
+ bucket = _FakeBucket()
142
+ session = _FakeSession(bucket)
143
+ _seed(_FakeClient(session))
144
+
145
+ comicvine.close_shared_sessions()
146
+
147
+ assert bucket.closed == 1
148
+ assert bucket.registry_was_empty is True
149
+ assert not session.registry
150
+
151
+
152
+ def test_close_survives_a_bucket_that_will_not_close() -> None:
153
+ """One bad bucket must not stop the rest, nor fail the caller."""
154
+ stuck = _StuckBucket()
155
+ fine = _FakeBucket()
156
+ session = _FakeSession(stuck, fine)
157
+ _seed(_FakeClient(session))
158
+
159
+ comicvine.close_shared_sessions()
160
+
161
+ assert stuck.closed == 1
162
+ assert fine.closed == 1
163
+ assert session.closed == 1
164
+
165
+
166
+ def test_a_closed_client_releases_every_sqlite_handle(tmp_path: Path) -> None:
167
+ """
168
+ The response cache's connection, and one per rate-limit pool.
169
+
170
+ `session.close()` alone gets the first; the buckets are
171
+ pyrate-limiter's and have to be closed by name (see
172
+ `_close_limiter_buckets`).
173
+ """
174
+ client = build_comicvine_client(tmp_path)
175
+ spend(client, "issues", 2)
176
+ spend(client, "search", 1)
177
+ buckets = _buckets(client)
178
+ assert len(buckets) == 2
179
+ assert all(bucket.conn is not None for bucket in buckets)
180
+
181
+ comicvine.close_client(client)
182
+
183
+ assert all(bucket.conn is None for bucket in buckets)
184
+ assert client._session.cache.responses._connection is None
185
+
186
+
187
+ def test_a_closed_client_emits_no_resource_warning(tmp_path: Path) -> None:
188
+ """The symptom that asked for this: unclosed sqlite handles."""
189
+ with warnings.catch_warnings(record=True) as caught:
190
+ warnings.simplefilter("always")
191
+ client = build_comicvine_client(tmp_path)
192
+ spend(client, "issues", 1)
193
+ spend(client, "search", 1)
194
+
195
+ comicvine.close_client(client)
196
+
197
+ del client
198
+ gc.collect()
199
+
200
+ leaked = [
201
+ str(warning.message)
202
+ for warning in caught
203
+ if issubclass(warning.category, ResourceWarning)
204
+ ]
205
+ assert not leaked
206
+
207
+
208
+ def test_a_closed_client_is_still_usable(tmp_path: Path) -> None:
209
+ """
210
+ Not a cancel: the client works after a close, and remembers.
211
+
212
+ The hourly budget lives in the bucket file, so the rebuilt bucket
213
+ picks up where the closed one left off.
214
+ """
215
+ client = build_comicvine_client(tmp_path)
216
+ spend(client, "issues", 3)
217
+
218
+ comicvine.close_client(client)
219
+ spend(client, "issues", 1)
220
+
221
+ status = comicvine.shared_client_rate_limit_status(
222
+ OnlineSettings(cache=OnlineCacheSettings(dir=tmp_path))
223
+ )
224
+ assert status["issues"]["remaining"] == COMICVINE_DEFAULT_PER_HOUR - 4
225
+ comicvine.close_client(client)
226
+
227
+
228
+ def test_close_is_safe_to_repeat(tmp_path: Path) -> None:
229
+ """A second close finds nothing to do rather than raising."""
230
+ client = build_comicvine_client(tmp_path)
231
+ spend(client, "issues", 1)
232
+
233
+ comicvine.close_client(client)
234
+ comicvine.close_client(client) # must not raise
235
+
236
+
237
+ def test_the_runner_closes_shared_clients(
238
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
239
+ ) -> None:
240
+ """A finished run releases what its Comic Vine lookups opened."""
241
+ from comicbox.run import Runner
242
+
243
+ calls: list[int] = []
244
+ monkeypatch.setattr(comicvine, "close_shared_sessions", lambda: calls.append(1))
245
+ comic = tmp_path / "file_0.cbz"
246
+ comic.write_bytes(b"") # zero-byte cbz; Comicbox will fail to open it
247
+ runner = Runner(
248
+ Namespace(comicbox=Namespace(paths=[str(comic)], print=Namespace(phases="p")))
249
+ )
250
+
251
+ runner.run()
252
+
253
+ assert calls == [1]
254
+
255
+
256
+ def test_online_session_close_releases_both_sources(
257
+ monkeypatch: pytest.MonkeyPatch,
258
+ ) -> None:
259
+ """`OnlineSession.close()` is not a Metron-only release."""
260
+ from comicbox.formats.metron_api import online_source as metron
261
+ from comicbox.online_session import OnlineCredentials, OnlineSession
262
+
263
+ calls: list[str] = []
264
+ monkeypatch.setattr(
265
+ comicvine, "close_shared_sessions", lambda: calls.append("comicvine")
266
+ )
267
+ monkeypatch.setattr(metron, "close_shared_sessions", lambda: calls.append("metron"))
268
+ credentials = OnlineCredentials(comicvine_key="k")
269
+
270
+ with OnlineSession(sources={"comicvine"}, credentials=credentials):
271
+ pass
272
+
273
+ assert calls == ["metron", "comicvine"]
@@ -14,24 +14,17 @@ def close_comicvine_client(client: Any) -> None:
14
14
  """
15
15
  Release every sqlite connection a simyan `Comicvine` opened.
16
16
 
17
- `session.close()` alone leaves the rate-limit buckets open:
18
- requests-ratelimiter's `HostBucketFactory` keeps its buckets in its
19
- own `buckets` dict, which pyrate-limiter's `BucketFactory.close()`
20
- does not iterate, so it closes the leaker but not the bucket
21
- connections. Closing them here keeps tests from leaking sqlite
22
- handles and emitting ResourceWarnings.
17
+ Delegates to the production close rather than keeping a second copy
18
+ of the bucket dance it has to do.
23
19
  """
24
- session = client._session
25
- factory = session.limiter.bucket_factory
26
- session.close()
27
- for bucket in list(getattr(factory, "buckets", {}).values()):
28
- bucket.close()
20
+ from comicbox.formats.comicvine_api.online_source import close_client
29
21
 
22
+ close_client(client)
30
23
 
31
- @contextmanager
32
- def comicvine_client(cache_dir: Path, api_key: str = "k") -> Generator[Any]:
24
+
25
+ def build_comicvine_client(cache_dir: Path, api_key: str = "k") -> Any:
33
26
  """
34
- Yield a real `Comicvine` pointed at `cache_dir`, fully closed on exit.
27
+ Build a real `Comicvine` pointed at `cache_dir`.
35
28
 
36
29
  Its file names match what `ComicVineOnlineSource` would choose, so a
37
30
  status read against the same directory finds them.
@@ -39,11 +32,17 @@ def comicvine_client(cache_dir: Path, api_key: str = "k") -> Generator[Any]:
39
32
  from simyan.comicvine import Comicvine
40
33
 
41
34
  cache_dir.mkdir(parents=True, exist_ok=True)
42
- client = Comicvine(
35
+ return Comicvine(
43
36
  api_key=api_key,
44
37
  cache_path=cache_dir / "comicvine_cache.sqlite",
45
38
  ratelimit_path=cache_dir / "comicvine_rate_limit.sqlite",
46
39
  )
40
+
41
+
42
+ @contextmanager
43
+ def comicvine_client(cache_dir: Path, api_key: str = "k") -> Generator[Any]:
44
+ """Yield a real `Comicvine` pointed at `cache_dir`, fully closed on exit."""
45
+ client = build_comicvine_client(cache_dir, api_key)
47
46
  try:
48
47
  yield client
49
48
  finally:
@@ -876,7 +876,7 @@ wheels = [
876
876
 
877
877
  [[package]]
878
878
  name = "comicbox"
879
- version = "5.2.0"
879
+ version = "5.2.1"
880
880
  source = { editable = "." }
881
881
  dependencies = [
882
882
  { name = "bidict", version = "0.23.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes