comicbox 5.1.2__tar.gz → 5.2.0__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.
- {comicbox-5.1.2 → comicbox-5.2.0}/.gitignore +1 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/NEWS.md +28 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/PKG-INFO +17 -6
- {comicbox-5.1.2 → comicbox-5.2.0}/README.md +15 -4
- {comicbox-5.1.2 → comicbox-5.2.0}/bun.lock +18 -18
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/write.py +39 -25
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/dump.py +18 -3
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/dump_files.py +7 -4
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/online/settings.py +7 -4
- comicbox-5.2.0/comicbox/exceptions.py +102 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/outcome_stats.py +13 -8
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/profile.py +8 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/rate_limits.py +5 -3
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/retry.py +1 -1
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicvine_api/online_source.py +20 -4
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_api/online_source.py +89 -15
- comicbox-5.2.0/comicbox/formats/metron_api/paced_session.py +306 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/online_session.py +25 -0
- comicbox-5.2.0/comicbox/predict.py +72 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/run.py +23 -3
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/write.py +170 -53
- {comicbox-5.1.2 → comicbox-5.2.0}/docs/NEWS.md +28 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/docs/README.md +15 -4
- {comicbox-5.1.2 → comicbox-5.2.0}/package.json +3 -3
- {comicbox-5.1.2 → comicbox-5.2.0}/pyproject.toml +2 -2
- {comicbox-5.1.2 → comicbox-5.2.0}/pyproject.toml.orig +2 -2
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/conftest.py +7 -7
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_comicvine_client.py +73 -0
- comicbox-5.2.0/tests/unit/test_destination_occupied_error.py +99 -0
- comicbox-5.2.0/tests/unit/test_metron_session_lifecycle.py +183 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_metron_source.py +46 -1
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_outcome_stats.py +34 -0
- comicbox-5.2.0/tests/unit/test_paced_session.py +459 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rate_gate_concurrency.py +77 -57
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rate_limits.py +10 -8
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rename_and_convert_safety.py +34 -14
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_write_api.py +6 -1
- comicbox-5.2.0/tests/unit/test_write_destination_prediction.py +172 -0
- comicbox-5.2.0/tests/unit/test_write_destination_safety.py +376 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_write_order_and_claims.py +8 -6
- comicbox-5.2.0/tests/util/metron_transport.py +142 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/uv.lock +84 -84
- comicbox-5.1.2/comicbox/exceptions.py +0 -49
- comicbox-5.1.2/comicbox/formats/metron_api/paced_session.py +0 -273
- comicbox-5.1.2/tests/unit/test_paced_session.py +0 -469
- {comicbox-5.1.2 → comicbox-5.2.0}/.dockerignore +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/.prettierignore +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/.readthedocs.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/.remarkignore +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/.shellcheckrc +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/Makefile +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/ci-download-dist-if-identical.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/clean-pycache.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/cleanup-pypi-alpha.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/dev-module.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/docker-compose-exit.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/docker-tag-latest.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/fix-docker.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/fix-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/fix-sh.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/fix.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/kill-eslint_d.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-ci.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-complexity.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-darwin.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-docker.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint-sh.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/lint.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/prettier-nginx.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/publish-pypi.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/regenerate-pdf-test-pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/release-tag.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/remark-for-claude.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/roman.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/sort-ignore.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/test-docker.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/test-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/type-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/uml.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/update-deps-node.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/update-deps-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/version-node.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/bin/version-python.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/ci.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/common.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/docker.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/docs.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/eslint.config.base.js +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/gha_std.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/help.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/node.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/node_root.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/cfg/python.mk +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/check-jsonschema.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/_pdf.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/_rar.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/archive.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/archiveinfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/filenames.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/init.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/mtime.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/read.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/archive/sniff.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/date.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/issue.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/notes.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/stamp.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/stories_title.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/url_identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/computed/urls.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/init.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/load.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/merge.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/metadata.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/normalize.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/online_covers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/online_lookup.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/pages/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/pages/covers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/pages/extract.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/print.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/sources.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/types.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/validate/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/box/validate/guess_format.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/cli/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/cli/__main__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/cli/epilog.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/cli/parser.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/computed.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/formats.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/online/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/online/build.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/online/template.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/paths.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/read.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config/settings.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/config_default.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/constants.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/empty.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/comet.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/comicbookinfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/comicbox.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/comicinfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/generic/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/generic/age_rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/generic/role.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/age_rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/formats.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/reading_direction.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/maps/roles.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/enums/metroninfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/events.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/_base.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/collection_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/comicbox.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/comicinfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/enum_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/metroninfo.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/number_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/pdf.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/pycountry.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/time_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/union.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/fields/xml_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/auto_engage.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/cli_overrides.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/cover_hash.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/credentials.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/matcher.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/prompt.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/rate_gate.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/sanitize.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/selector.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/series_cache.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/series_filter.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/session_state.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/signals.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/sources/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/sources/base.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/transform_helpers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/vacuum.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/online/warn_once.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/base.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/cache.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/decorators.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/error_store.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/json_schemas.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/xml_schemas.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/schemas/yaml.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/base.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/price.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/publishing_tags.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/spec.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/xml_credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/base/transforms/xml_reprints.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comet/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comet/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comet/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comet/transform/reprints.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_book_info/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_book_info/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_book_info/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_book_info/transform/credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_book_info/transform/rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/transform/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/transform/manga.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/transform/pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comic_info/transform/storyarcs.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/README.md +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/cli.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/json_schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/publishing.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/schema/yaml.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/cli.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/json.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/name_objs.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicbox/transform/yaml.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicvine_api/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicvine_api/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/comicvine_api/transform.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/filename/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/filename/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/filename/transform.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_api/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_api/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_api/transform.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/price.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/publishing.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/schema/resource.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/const.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/identified_name.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/identifier_attribute.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/publishing_tags.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/reprints.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/metron_info/transform/resources.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/pdf/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/pdf/schema.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/pdf/transform/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/pdf/transform/credits.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/formats/sources.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/identifiers/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/identifiers/identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/identifiers/other.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/identifiers/urns.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/logger.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/merge/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/merge/mergedeep.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/online_estimate.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/print.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/process.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/py.typed +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/CoMet-v1.1.xsd +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/ComicInfo-v2.1-Draft.xsd +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/MetronInfo-v1.1.xsd +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/comic-book-info-v1.0.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v1.0/comicbox-v1.0.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v1.0/page.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v1.0/reprint.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v1.0/series.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v1.0/volume.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/comicbox-v2.0.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/comicbox.example.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/credit.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/date.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/identified-object.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/identified-objects.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/identifiers.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/named-identified-object.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/page.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/reprint.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/series.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v2.0/volume.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/alternative-name.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/comicbox-v3.0.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/comicbox.example.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/credit.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/date.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/identified-object.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/identified-objects.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/identifiers.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/named-identified-object.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/page.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/reprint.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/role.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/series.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/schemas/v3.0/volume.schema.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/base.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/json_validator.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/spec.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/xml_validator.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/validate/yaml_validator.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/comicbox/version.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/compose.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/docs/requirements.txt +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/docs/style.css +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/docs/style.mkdocs.css +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/eslint.config.js +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/generate-schema/gen-schema.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/generate-schema/mjs.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/mkdocs.yml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/.gitignore +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/README.md +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/_harness_helpers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/bootstrap.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/compare.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/debug_search.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/fixtures.example.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/label_metron.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/run.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/sample.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/summarize.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_bootstrap.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_compare.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_harness.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_harness_checkpoint.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_harness_diagnostics.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_harness_filter.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_harness_resume.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_label_metron.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_sample.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/calibration/test_summarize.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_exit_code.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_export.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_extract.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_online.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_print.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_rename.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/cli/test_cli_write.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/const.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001 (1950) The Beginning - multi.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001 (1950) The Beginning - nothing.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-cbi.cbr +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-cix-cbi.cbr +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-cix.cbt +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-cix.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-comet.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001-metron.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001.cb7 +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science #001.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CaptainScience#1_01.jpg +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CaptainScience#1_02.jpg +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CaptainScience#1_03.jpg +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CaptainScience#1_04.jpg +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CaptainScience#1_05.jpg +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/CoMet.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/comic-book-info.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/comicinfo-original.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/comicinfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/metroninfo-full.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/Captain Science 001/metroninfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/comicbox-cli.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/comicbox.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/create-archive.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/create_cs_archives.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/create_macos_resource_fork.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/empty.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/examples/comic-book-info-example.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/examples/metroninfo-v1.1-valid.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comet.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comic-book-info.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comicbox-cli.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comicbox.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comicbox.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/comicinfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/create-export-archive.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/metroninfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/mupdf.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export/pdf-metadata.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/export.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/list_recurse_output.txt +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/macos_resource_fork.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comet-cover-path-list.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comet-write.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comet.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comic-book-info-example.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comic-book-info.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox-cli.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox-filename.txt +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox-write.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox-write.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicbox.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-bad-multiple.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-cover-path-list.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-ids-from-tags.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-metron-origin.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-notes-date.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-validation.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo-write.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/comicinfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/create-archives.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/metroninfo-no-primary-id.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/metroninfo-v1.1-valid.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/metroninfo-write.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/metroninfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/mupdf-nested.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/mupdf.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/pdf-cix.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/pdf-nested.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/metadata/pdf.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comet.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comic-book-info.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comicbox-filename.txt +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comicbox.json +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comicbox.yaml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/comicinfo.xml +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/multi/create-multi-archive.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/patchme.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/pdf/0.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/pdf/1.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/pdf/2.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/pdf/3.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/test_cix.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/test_pdf.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/test_pdf_legacy_keywords.pdf +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/xml.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/files/yaml.cbz +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_cbi.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_cix.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_comet.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_filename.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_json.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_json_schema_drift.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_metron.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_multi.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_pdf_json.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_pdf_xml.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_validation.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/schemas/test_yaml.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/.gitignore +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/README.md +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/jobs_accuracy.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/prompt_ux.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/run.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/test_jobs_accuracy.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/test_prompt_ux.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/stress/test_run.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_codex_api.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_compute.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_convert_pdf.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_extract.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_import_export.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_mtime.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_pages.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_stories_title.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_validate_testfiles.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/test_writing.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_archive_errors.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_archive_read.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_archiveinfo_mtime.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_auto_engage.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_box_cache_invalidation.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_cbi_rating_normalize.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_cli_choices_enum_drift.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_cli_dest_folds.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_cli_dest_template_drift.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_comicinfo_alternates.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_comicinfo_series_groups.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_comicvine.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_computed_pipeline.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_computed_reprints.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_container_inputs.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_defaults_drift.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_env_tree.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_file_option.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_layering.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_no_path_guard.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_online_leaf.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_config_online_sources.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_cover_hash.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_credit_primary.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_defer_prompts.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_error_store_visibility.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_exceptions.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_lazy_startup.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_library_logging.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_list_field_dedupe.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_list_field_sort_key_types.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_manga_and_age_rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_manga_volume.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_merge_mode_config.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_merge_order.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_merge_precedence.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_mergedeep.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_metron_community_rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_metron_id_attribute_fallback.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_metron_prefetch.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_metron_transform.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_abort_propagation.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_degradation.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_estimate.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_events.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_lookup.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_matcher.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_matcher_resolution.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_matcher_scoring.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_prompt.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_prompt_loops.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_result_contract.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_retry.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_selector.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_session.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_online_session_state.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_parse_decimal.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_parse_issue.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_pdf_pages_help.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_process.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_prompt_dedup.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_quota_reserved_skip.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rar_time_overflow.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rate_gate.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_read_events.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_refresh_cache.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_reprints_and_alternative_names.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_rich_transforms.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_role_vocabulary.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_run_parallel.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_run_recurse.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_series_cache.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_series_filter.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_series_single_flight.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_session_series_batching.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_single_valued_tags.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_sniff.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_sources.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_to_metron_age_rating.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_transform_spec_dest_collisions.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_transform_spec_missing_keys.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_transform_spec_scope_phases.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_urls_and_identifiers.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_vacuum.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_validate.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_xml_cdata_fields.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/unit/test_yes_no_field.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/__init__.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/compare.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/diff.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/metadata.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/online_client.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/online_matcher.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/online_prompt.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/parser.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/util/tmp.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/tests/validate_cli.py +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/validate-schema.sh +0 -0
- {comicbox-5.1.2 → comicbox-5.2.0}/vulture_ignorelist.py +0 -0
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# 📰 News
|
|
2
2
|
|
|
3
|
+
## v5.2.0
|
|
4
|
+
|
|
5
|
+
- Features
|
|
6
|
+
- `comicbox.predict.predict_write_destination()` reports where a write would
|
|
7
|
+
land, and whether that path is taken, before anything is written.
|
|
8
|
+
- A destination collision raises `DestinationOccupiedError`, which names the
|
|
9
|
+
occupied path and says which kind of collision it was.
|
|
10
|
+
- Match candidates carry a full-size cover url alongside the thumbnail.
|
|
11
|
+
- `OnlineSession.close()` releases the pooled Metron connections; the CLI
|
|
12
|
+
does this at the end of every run.
|
|
13
|
+
|
|
14
|
+
- Fixes
|
|
15
|
+
- `bulk_write` checks every destination before doing any work, so a batch
|
|
16
|
+
fails fast instead of repacking an archive and refusing it at the end.
|
|
17
|
+
`preflight=False` restores the old behavior.
|
|
18
|
+
- Two archives in one batch that convert to the same name now lose
|
|
19
|
+
deterministically, by submission order, instead of by thread race.
|
|
20
|
+
- A kept-original CBR whose CBZ twin already exists is refused before its
|
|
21
|
+
metadata is read, on the command line as well as the API.
|
|
22
|
+
|
|
23
|
+
- Performance
|
|
24
|
+
- Metron requests reuse one pooled connection per run instead of a new TLS
|
|
25
|
+
handshake each (mokkari 4.8.0).
|
|
26
|
+
|
|
27
|
+
- Dependencies
|
|
28
|
+
- Requires mokkari 4.8.0. Metron pacing now registers through mokkari's
|
|
29
|
+
`rate_limiter` hook instead of overriding a private method.
|
|
30
|
+
|
|
3
31
|
## v5.1.2
|
|
4
32
|
|
|
5
33
|
- Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comicbox
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.0
|
|
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
|
|
@@ -23,7 +23,7 @@ Requires-Dist: keyring~=25.6
|
|
|
23
23
|
Requires-Dist: loguru~=0.7
|
|
24
24
|
Requires-Dist: marshmallow-union>=0.1.15.post1
|
|
25
25
|
Requires-Dist: marshmallow~=4.3
|
|
26
|
-
Requires-Dist: mokkari>=4.
|
|
26
|
+
Requires-Dist: mokkari>=4.8.0,<5
|
|
27
27
|
Requires-Dist: nh3>=0.3.5
|
|
28
28
|
Requires-Dist: pillow>=12.2.0
|
|
29
29
|
Requires-Dist: platformdirs>=4.9.6
|
|
@@ -314,12 +314,23 @@ result = write_metadata(
|
|
|
314
314
|
print(result.written)
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
+
`comicbox.predict.predict_write_destination` reports where a write would land —
|
|
318
|
+
in place, or the CBZ an unwritable archive repacks to — and whether that path is
|
|
319
|
+
already taken, without opening the archive or reading any metadata:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from comicbox.predict import predict_write_destination
|
|
323
|
+
|
|
324
|
+
dest = predict_write_destination("comic.cbr")
|
|
325
|
+
print(dest.destination, dest.converts, dest.occupied)
|
|
326
|
+
```
|
|
327
|
+
|
|
317
328
|
Every operational error these APIs raise derives from
|
|
318
329
|
`comicbox.exceptions.ComicboxError` — `ArchiveError`, `ArchiveWriteError`,
|
|
319
|
-
`
|
|
320
|
-
`OnlineConfigurationError`, `OnlineLookupAbortedError`,
|
|
321
|
-
`UnsupportedArchiveTypeError` — so consumers can `except ComicboxError`
|
|
322
|
-
swallowing unrelated programming errors.
|
|
330
|
+
`DestinationOccupiedError`, `MetadataError`, `ExportError`,
|
|
331
|
+
`WriteValidationError`, `OnlineConfigurationError`, `OnlineLookupAbortedError`,
|
|
332
|
+
and `UnsupportedArchiveTypeError` — so consumers can `except ComicboxError`
|
|
333
|
+
without swallowing unrelated programming errors.
|
|
323
334
|
|
|
324
335
|
## ⚙️ Configuration
|
|
325
336
|
|
|
@@ -262,12 +262,23 @@ result = write_metadata(
|
|
|
262
262
|
print(result.written)
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
+
`comicbox.predict.predict_write_destination` reports where a write would land —
|
|
266
|
+
in place, or the CBZ an unwritable archive repacks to — and whether that path is
|
|
267
|
+
already taken, without opening the archive or reading any metadata:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from comicbox.predict import predict_write_destination
|
|
271
|
+
|
|
272
|
+
dest = predict_write_destination("comic.cbr")
|
|
273
|
+
print(dest.destination, dest.converts, dest.occupied)
|
|
274
|
+
```
|
|
275
|
+
|
|
265
276
|
Every operational error these APIs raise derives from
|
|
266
277
|
`comicbox.exceptions.ComicboxError` — `ArchiveError`, `ArchiveWriteError`,
|
|
267
|
-
`
|
|
268
|
-
`OnlineConfigurationError`, `OnlineLookupAbortedError`,
|
|
269
|
-
`UnsupportedArchiveTypeError` — so consumers can `except ComicboxError`
|
|
270
|
-
swallowing unrelated programming errors.
|
|
278
|
+
`DestinationOccupiedError`, `MetadataError`, `ExportError`,
|
|
279
|
+
`WriteValidationError`, `OnlineConfigurationError`, `OnlineLookupAbortedError`,
|
|
280
|
+
and `UnsupportedArchiveTypeError` — so consumers can `except ComicboxError`
|
|
281
|
+
without swallowing unrelated programming errors.
|
|
271
282
|
|
|
272
283
|
## ⚙️ Configuration
|
|
273
284
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@fsouza/prettierd": "^0.29.0",
|
|
11
11
|
"@prettier/plugin-xml": "^3.4.2",
|
|
12
12
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
13
|
-
"eslint": "^10.
|
|
13
|
+
"eslint": "^10.11.0",
|
|
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",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"eslint-plugin-no-secrets": "^2.3.3",
|
|
24
24
|
"eslint-plugin-no-unsanitized": "^4.1.5",
|
|
25
25
|
"eslint-plugin-no-use-extend-native": "^0.7.3",
|
|
26
|
-
"eslint-plugin-package-json": "^1.
|
|
26
|
+
"eslint-plugin-package-json": "^1.9.0",
|
|
27
27
|
"eslint-plugin-perfectionist": "^5.11.1",
|
|
28
28
|
"eslint-plugin-prettier": "^5.5.6",
|
|
29
29
|
"eslint-plugin-promise": "^7.3.0",
|
|
30
|
-
"eslint-plugin-regexp": "^3.3.
|
|
30
|
+
"eslint-plugin-regexp": "^3.3.1",
|
|
31
31
|
"eslint-plugin-security": "^4.0.1",
|
|
32
32
|
"eslint-plugin-sonarjs": "^4.2.1",
|
|
33
33
|
"eslint-plugin-toml": "^1.5.0",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
|
|
53
53
|
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
|
54
54
|
|
|
55
|
-
"@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.
|
|
55
|
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.6", "", {}, "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA=="],
|
|
56
56
|
|
|
57
|
-
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.
|
|
57
|
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.6", "", {}, "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ=="],
|
|
58
58
|
|
|
59
|
-
"@babel/parser": ["@babel/parser@8.0.
|
|
59
|
+
"@babel/parser": ["@babel/parser@8.0.6", "", { "dependencies": { "@babel/types": "^8.0.6" }, "bin": "./bin/babel-parser.js" }, "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg=="],
|
|
60
60
|
|
|
61
|
-
"@babel/types": ["@babel/types@8.0.
|
|
61
|
+
"@babel/types": ["@babel/types@8.0.6", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.6", "@babel/helper-validator-identifier": "^8.0.6" } }, "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A=="],
|
|
62
62
|
|
|
63
63
|
"@cacheable/memory": ["@cacheable/memory@2.2.0", "", { "dependencies": { "@cacheable/utils": "^2.5.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ=="],
|
|
64
64
|
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
|
|
167
167
|
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
|
168
168
|
|
|
169
|
-
"@types/node": ["@types/node@26.6.
|
|
169
|
+
"@types/node": ["@types/node@26.6.2", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g=="],
|
|
170
170
|
|
|
171
171
|
"@types/supports-color": ["@types/supports-color@8.1.3", "", {}, "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg=="],
|
|
172
172
|
|
|
@@ -338,11 +338,11 @@
|
|
|
338
338
|
|
|
339
339
|
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
|
340
340
|
|
|
341
|
-
"electron-to-chromium": ["electron-to-chromium@1.5.
|
|
341
|
+
"electron-to-chromium": ["electron-to-chromium@1.5.433", "", {}, "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA=="],
|
|
342
342
|
|
|
343
343
|
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
|
344
344
|
|
|
345
|
-
"empathic": ["empathic@2.0
|
|
345
|
+
"empathic": ["empathic@2.1.0", "", {}, "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw=="],
|
|
346
346
|
|
|
347
347
|
"entities": ["entities@8.1.0", "", {}, "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA=="],
|
|
348
348
|
|
|
@@ -354,11 +354,11 @@
|
|
|
354
354
|
|
|
355
355
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
|
356
356
|
|
|
357
|
-
"eslint": ["eslint@10.
|
|
357
|
+
"eslint": ["eslint@10.11.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ=="],
|
|
358
358
|
|
|
359
359
|
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": "bin/cli.js" }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
|
|
360
360
|
|
|
361
|
-
"eslint-fix-utils": ["eslint-fix-utils@0.4.
|
|
361
|
+
"eslint-fix-utils": ["eslint-fix-utils@0.4.4", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-9reP0fw++eZv0qs0ji8U1G9a+j8yWQW1RT8Wrlw32JlhQJDiweVX98kyDrJq6G7jdOLgKwmj0nPghyMadsOgHQ=="],
|
|
362
362
|
|
|
363
363
|
"eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" } }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="],
|
|
364
364
|
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
|
|
389
389
|
"eslint-plugin-no-use-extend-native": ["eslint-plugin-no-use-extend-native@0.7.3", "", { "dependencies": { "is-get-set-prop": "^2.0.0", "is-js-type": "^3.0.0", "is-obj-prop": "^2.0.0", "is-proto-prop": "^3.0.1" }, "peerDependencies": { "eslint": "^9.3.0 || ^10.0.0" } }, "sha512-kYJhgZkiZIavu/wIwrO+n4GemQcMX53kWCNZNr7nGMkRD1aBFLkDpBivEYP7nIJINCo9fzPbFjrpeX5kr2Qbww=="],
|
|
390
390
|
|
|
391
|
-
"eslint-plugin-package-json": ["eslint-plugin-package-json@1.
|
|
391
|
+
"eslint-plugin-package-json": ["eslint-plugin-package-json@1.9.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-Q18c0/YIAYhtlLoBzqA6qNO2dJmNHARlIARPROH9MZ/l0B1u/JafEtc0XNcuTXgYdvFUrhv649Z3sd5T4mO9ww=="],
|
|
392
392
|
|
|
393
393
|
"eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.11.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.70.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-xloHScnOpin9Jy3CegfzoBeY33ASk0spLyM2sJs0LyNHQjv9m0osUD2PYNT7NrJieZbUR2Ebn4A5Q0+0dOcv+A=="],
|
|
394
394
|
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
|
|
397
397
|
"eslint-plugin-promise": ["eslint-plugin-promise@7.3.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }, "sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA=="],
|
|
398
398
|
|
|
399
|
-
"eslint-plugin-regexp": ["eslint-plugin-regexp@3.3.
|
|
399
|
+
"eslint-plugin-regexp": ["eslint-plugin-regexp@3.3.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^9.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-58bGUci/AaWAo1ETj6Lsnmy6QXwnSyVONRA805828nnqp7vrTwdaQMc02vtowmYB3A5OB3SLr3r5HgmfxX4dLg=="],
|
|
400
400
|
|
|
401
401
|
"eslint-plugin-security": ["eslint-plugin-security@4.0.1", "", { "dependencies": { "safe-regex": "^2.1.1" } }, "sha512-/lZCkOxPOWaf1jXAqgICrS8St3BMBccIPvhOSUYuV6VCr1o5nFVG998FnTLt6w2Nxb8Uo0nM8fzmnhp+GY/aEg=="],
|
|
402
402
|
|
|
@@ -404,7 +404,7 @@
|
|
|
404
404
|
|
|
405
405
|
"eslint-plugin-toml": ["eslint-plugin-toml@1.5.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "debug": "^4.1.1", "toml-eslint-parser": "^1.0.1" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-qBjRywEkKxO2uYOjus//6GVF1r+Hg5QDkRO8RTY6XcaXgWfBU0DhwpFmJa2Ljf0Sz49r7DdZlpKwwHmJ4nmH1Q=="],
|
|
406
406
|
|
|
407
|
-
"eslint-plugin-unicorn": ["eslint-plugin-unicorn@
|
|
407
|
+
"eslint-plugin-unicorn": ["eslint-plugin-unicorn@76.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.10.1", "@eslint/css-tree": "^4.0.5", "browserslist": "^4.28.8", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.50.0", "detect-indent": "^7.0.2", "entities": "^8.0.0", "find-up-simple": "^1.0.1", "globals": "^17.11.0", "identifier-regex": "^1.1.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "pluralize": "^8.0.0", "quote-js-string": "^0.1.0", "regjsparser": "^0.13.2", "reserved-identifiers": "^1.2.0", "semver": "^7.8.5", "strip-indent": "^4.1.1", "yaml": "^2.9.0" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-3ywJrCMHKYhcBP5yFzGaOOX3dY7+iR3hvpuoS/0EnhRFgVWLejly+t4fOdJvWQxJdHZl1C+QBW7hybXPDld4Pw=="],
|
|
408
408
|
|
|
409
409
|
"eslint-plugin-yml": ["eslint-plugin-yml@3.8.1", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-E/70psRwxz5EJ8dBtzrFfqSiiInuSytbdtFCjueb/GcKjsWzPBfxfhtQePImMAPjHwMA/VDurO7DJwStDJ4wWg=="],
|
|
410
410
|
|
|
@@ -572,7 +572,7 @@
|
|
|
572
572
|
|
|
573
573
|
"lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="],
|
|
574
574
|
|
|
575
|
-
"lru-cache": ["lru-cache@11.5.
|
|
575
|
+
"lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="],
|
|
576
576
|
|
|
577
577
|
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
|
|
578
578
|
|
|
@@ -788,7 +788,7 @@
|
|
|
788
788
|
|
|
789
789
|
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": "bin/regexp-tree" }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
|
790
790
|
|
|
791
|
-
"regjsparser": ["regjsparser@0.13.
|
|
791
|
+
"regjsparser": ["regjsparser@0.13.3", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg=="],
|
|
792
792
|
|
|
793
793
|
"remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="],
|
|
794
794
|
|
|
@@ -1140,7 +1140,7 @@
|
|
|
1140
1140
|
|
|
1141
1141
|
"toml-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
|
1142
1142
|
|
|
1143
|
-
"unified-engine/@types/node": ["@types/node@22.20.
|
|
1143
|
+
"unified-engine/@types/node": ["@types/node@22.20.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA=="],
|
|
1144
1144
|
|
|
1145
1145
|
"unified-engine/ignore": ["ignore@6.0.2", "", {}, "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A=="],
|
|
1146
1146
|
|
|
@@ -10,7 +10,7 @@ from zipremove import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
|
|
10
10
|
from comicbox._pdf import PAGE_FORMAT_PIXMAP_JPEG
|
|
11
11
|
from comicbox.box.archive.archiveinfo import ArchiveInfo, InfoType
|
|
12
12
|
from comicbox.box.archive.read import ComicboxArchiveRead
|
|
13
|
-
from comicbox.exceptions import ArchiveWriteError
|
|
13
|
+
from comicbox.exceptions import ArchiveWriteError, DestinationOccupiedError
|
|
14
14
|
from comicbox.formats.sources import MetadataSources
|
|
15
15
|
|
|
16
16
|
_RECOMPRESS_SUFFIX = ".comicbox_tmp_zip"
|
|
@@ -26,13 +26,12 @@ _INFLIGHT_DESTINATIONS: set[Path] = set()
|
|
|
26
26
|
_INFLIGHT_GUARD = Lock()
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def _claim_destination(
|
|
29
|
+
def _claim_destination(destination: Path, source: Path) -> None:
|
|
30
30
|
"""Take a conversion destination, refusing one already in flight."""
|
|
31
31
|
with _INFLIGHT_GUARD:
|
|
32
|
-
if
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
_INFLIGHT_DESTINATIONS.add(path)
|
|
32
|
+
if destination in _INFLIGHT_DESTINATIONS:
|
|
33
|
+
raise DestinationOccupiedError(source, destination, "inflight")
|
|
34
|
+
_INFLIGHT_DESTINATIONS.add(destination)
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def _release_destination(path: Path) -> None:
|
|
@@ -53,15 +52,36 @@ _ALL_ARCHIVE_METADATA_FILENAMES = frozenset(
|
|
|
53
52
|
class ComicboxArchiveWrite(ComicboxArchiveRead):
|
|
54
53
|
"""Comicboxs methods for writing to the archive."""
|
|
55
54
|
|
|
56
|
-
def
|
|
55
|
+
def get_write_destination(self) -> Path:
|
|
56
|
+
"""
|
|
57
|
+
Where a write of this archive lands: in place, or the CBZ it repacks to.
|
|
58
|
+
|
|
59
|
+
Decided from the sniffed archive type (``_archive_cls`` /
|
|
60
|
+
``_archive_is_pdf``) and ``convert.cbz`` -- never from the suffix. A
|
|
61
|
+
zip is rewritten in place whatever it is called; a PDF is updated in
|
|
62
|
+
place unless ``convert.cbz`` asks for a repack; everything else
|
|
63
|
+
(CBR/CBT/CB7) is unwritable and repacks to ``<stem>.cbz``.
|
|
64
|
+
"""
|
|
57
65
|
if not self._path:
|
|
58
|
-
reason = "Cannot write
|
|
59
|
-
raise ArchiveWriteError(reason)
|
|
60
|
-
new_path = self._path.with_suffix(_CBZ_SUFFIX)
|
|
61
|
-
if new_path.is_file() and new_path != self._path:
|
|
62
|
-
reason = f"{new_path} already exists."
|
|
66
|
+
reason = "Cannot write archive metadata without a path."
|
|
63
67
|
raise ArchiveWriteError(reason)
|
|
64
|
-
|
|
68
|
+
is_zip = self._archive_cls == ZipFile
|
|
69
|
+
is_pdf_in_place = self._archive_is_pdf and not self._config.convert.cbz
|
|
70
|
+
if is_zip or is_pdf_in_place:
|
|
71
|
+
return self._path
|
|
72
|
+
return self._path.with_suffix(_CBZ_SUFFIX)
|
|
73
|
+
|
|
74
|
+
def _get_new_archive_path(self) -> Path:
|
|
75
|
+
"""Return the conversion destination, refusing one already on disk."""
|
|
76
|
+
destination = self.get_write_destination()
|
|
77
|
+
if destination != self._path and destination.is_file():
|
|
78
|
+
# self._path is non-None: get_write_destination raised otherwise.
|
|
79
|
+
raise DestinationOccupiedError(
|
|
80
|
+
self._path, # pyright: ignore[reportArgumentType], # ty: ignore[invalid-argument-type]
|
|
81
|
+
destination,
|
|
82
|
+
"convert",
|
|
83
|
+
)
|
|
84
|
+
return destination
|
|
65
85
|
|
|
66
86
|
def _cleanup_tmp_archive(self, tmp_path: Path, new_path: Path) -> None:
|
|
67
87
|
if not self._archive_cls or not self._path:
|
|
@@ -234,26 +254,20 @@ class ComicboxArchiveWrite(ComicboxArchiveRead):
|
|
|
234
254
|
|
|
235
255
|
Every write claims its destination, not just the conversions. An
|
|
236
256
|
in-place rewrite is as destructive as a conversion when two writers
|
|
237
|
-
overlap on it -- ``bulk_write``
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
still in flight.
|
|
257
|
+
overlap on it -- ``bulk_write``'s preflight refuses a path named
|
|
258
|
+
twice; the claim is the backstop for concurrent callers -- and the
|
|
259
|
+
finished-file check cannot see a write that is still in flight.
|
|
241
260
|
"""
|
|
242
261
|
if not self._path:
|
|
243
262
|
reason = "Cannot write archive metadata without a path."
|
|
244
263
|
raise ArchiveWriteError(reason)
|
|
264
|
+
destination = self.get_write_destination()
|
|
245
265
|
is_zip = self._archive_cls == ZipFile
|
|
246
|
-
|
|
247
|
-
destination = (
|
|
248
|
-
self._path
|
|
249
|
-
if is_zip or is_pdf_in_place
|
|
250
|
-
else self._path.with_suffix(_CBZ_SUFFIX)
|
|
251
|
-
)
|
|
252
|
-
_claim_destination(destination)
|
|
266
|
+
_claim_destination(destination, self._path)
|
|
253
267
|
try:
|
|
254
268
|
if is_zip:
|
|
255
269
|
self._patch_zipfile(files, comment)
|
|
256
|
-
elif
|
|
270
|
+
elif destination == self._path:
|
|
257
271
|
self._update_pdffile(files, mupdf_metadata)
|
|
258
272
|
else:
|
|
259
273
|
self._create_zipfile(files, comment)
|
|
@@ -146,16 +146,31 @@ class ComicboxDump(ComicboxPages):
|
|
|
146
146
|
}
|
|
147
147
|
self._reset_archive(None, None, keep_sources)
|
|
148
148
|
|
|
149
|
+
def _is_write_disabled(self) -> bool:
|
|
150
|
+
"""Return whether the config asks for no write at all."""
|
|
151
|
+
write = self._config.write
|
|
152
|
+
return self._config.general.dry_run or not (
|
|
153
|
+
write.formats or self._config.convert.cbz or write.delete_all_tags
|
|
154
|
+
)
|
|
155
|
+
|
|
149
156
|
def dump(self, formats: frozenset[MetadataFormats] | None = None) -> None:
|
|
150
157
|
"""Write metadata according to config.write settings."""
|
|
151
158
|
write = self._config.write
|
|
152
159
|
convert = self._config.convert
|
|
153
|
-
if self.
|
|
154
|
-
write.formats or convert.cbz or write.delete_all_tags
|
|
155
|
-
):
|
|
160
|
+
if self._is_write_disabled():
|
|
156
161
|
logger.info(f"Not writing metadata for: {self._path}")
|
|
157
162
|
return None
|
|
158
163
|
|
|
164
|
+
if self._path:
|
|
165
|
+
# Refuse an occupied conversion destination before any metadata
|
|
166
|
+
# is read. _get_dump_formats() below loads and merges every
|
|
167
|
+
# source, which is the expensive part of a write that was only
|
|
168
|
+
# ever going to be refused -- a kept-original CBR whose CBZ twin
|
|
169
|
+
# already exists is the common case, and it reaches here from
|
|
170
|
+
# the CLI as well as the API. _create_zipfile repeats the check
|
|
171
|
+
# (it is idempotent) so direct callers stay guarded.
|
|
172
|
+
self._get_new_archive_path()
|
|
173
|
+
|
|
159
174
|
# Must get metadata *before* get write formats
|
|
160
175
|
if formats is None:
|
|
161
176
|
formats = self._get_dump_formats()
|
|
@@ -7,7 +7,11 @@ from loguru import logger
|
|
|
7
7
|
|
|
8
8
|
from comicbox.box.archive.write import _claim_destination, _release_destination
|
|
9
9
|
from comicbox.box.dump import ComicboxDump
|
|
10
|
-
from comicbox.exceptions import
|
|
10
|
+
from comicbox.exceptions import (
|
|
11
|
+
ArchiveWriteError,
|
|
12
|
+
DestinationOccupiedError,
|
|
13
|
+
ExportError,
|
|
14
|
+
)
|
|
11
15
|
from comicbox.formats import MetadataFormats
|
|
12
16
|
|
|
13
17
|
|
|
@@ -96,11 +100,10 @@ class ComicboxDumpToFiles(ComicboxDump):
|
|
|
96
100
|
"""
|
|
97
101
|
if self._is_same_file(old_path, new_path):
|
|
98
102
|
return
|
|
99
|
-
_claim_destination(new_path)
|
|
103
|
+
_claim_destination(new_path, old_path)
|
|
100
104
|
try:
|
|
101
105
|
if new_path.exists():
|
|
102
|
-
|
|
103
|
-
raise ArchiveWriteError(reason)
|
|
106
|
+
raise DestinationOccupiedError(old_path, new_path, "rename")
|
|
104
107
|
old_path.rename(new_path)
|
|
105
108
|
finally:
|
|
106
109
|
_release_destination(new_path)
|
|
@@ -168,10 +168,13 @@ class OnlineSourceLimits:
|
|
|
168
168
|
``comicbox.formats.base.online.rate_limits`` for citation / audit.
|
|
169
169
|
"""
|
|
170
170
|
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
171
|
+
# Metron overrides. mokkari tracks Metron's actual per-user limits
|
|
172
|
+
# from `X-RateLimit-*` response headers rather than a fixed local
|
|
173
|
+
# bucket. `per_minute` has been comicbox's `RateGate` ceiling since
|
|
174
|
+
# 5.1.0 -- it can lower the server-reported burst limit, never raise
|
|
175
|
+
# it, which is how several processes on one token split the window.
|
|
176
|
+
# `per_day` is still accepted and ignored with a warning at client
|
|
177
|
+
# build (see `_warn_ignored_rate_limit_overrides`).
|
|
175
178
|
per_minute: int | None = None
|
|
176
179
|
per_day: int | None = None
|
|
177
180
|
# Historical ComicVine overrides. simyan builds its rate limiter
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exceptions for comicbox.
|
|
3
|
+
|
|
4
|
+
Every operational error comicbox raises on a public path derives from
|
|
5
|
+
:class:`ComicboxError`, so library consumers can write ``except
|
|
6
|
+
ComicboxError`` without also swallowing unrelated programming errors
|
|
7
|
+
(``ValueError``, ``KeyError``, …) from their own code.
|
|
8
|
+
|
|
9
|
+
This module is a leaf: it must not import anything from comicbox, so any
|
|
10
|
+
module (including the format packages and the box mixins) can import it
|
|
11
|
+
without load-order concerns. The write/online modules re-export their
|
|
12
|
+
exceptions from here under their historical import paths.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Literal
|
|
17
|
+
|
|
18
|
+
from typing_extensions import override
|
|
19
|
+
|
|
20
|
+
DestinationKind = Literal["convert", "rename", "inflight"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ComicboxError(Exception):
|
|
24
|
+
"""Base class for all operational errors comicbox raises."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UnsupportedArchiveTypeError(ComicboxError):
|
|
28
|
+
"""Unsupported Archive Type."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ArchiveError(ComicboxError):
|
|
32
|
+
"""An archive could not be opened or read."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ArchiveWriteError(ArchiveError):
|
|
36
|
+
"""An archive could not be written, repacked, or renamed."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DestinationOccupiedError(ArchiveWriteError):
|
|
40
|
+
"""
|
|
41
|
+
A write's destination path is held by another file or writer.
|
|
42
|
+
|
|
43
|
+
``kind`` says which collision: ``"convert"`` -- the CBZ this archive
|
|
44
|
+
would repack to already exists (or another archive in the same batch
|
|
45
|
+
claims it, in which case ``occupant`` names it); ``"rename"`` -- the
|
|
46
|
+
predicted filename is taken; ``"inflight"`` -- another writer in this
|
|
47
|
+
process holds the destination right now (transient; retry later).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
source: Path,
|
|
53
|
+
destination: Path,
|
|
54
|
+
kind: DestinationKind,
|
|
55
|
+
occupant: Path | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Record the two paths, the collision kind, and any rival source."""
|
|
58
|
+
self.source = source
|
|
59
|
+
self.destination = destination
|
|
60
|
+
self.kind = kind
|
|
61
|
+
self.occupant = occupant
|
|
62
|
+
super().__init__(self._message())
|
|
63
|
+
|
|
64
|
+
def _message(self) -> str:
|
|
65
|
+
"""Render the frozen message text for this collision kind."""
|
|
66
|
+
if self.kind == "inflight":
|
|
67
|
+
return f"{self.destination} is already being written by another archive."
|
|
68
|
+
if self.occupant is not None:
|
|
69
|
+
return f"{self.destination} is also the destination of {self.occupant}."
|
|
70
|
+
return f"{self.destination} already exists."
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
def __reduce__(self) -> tuple:
|
|
74
|
+
"""
|
|
75
|
+
Rebuild with the real signature when crossing a process boundary.
|
|
76
|
+
|
|
77
|
+
Python unpickles an exception as ``cls(*self.args)``, which here
|
|
78
|
+
is ``cls(message)`` -- a TypeError that would replace the real
|
|
79
|
+
error with a bogus one on the far side. WriteResult.error is a
|
|
80
|
+
public field, so keep it picklable.
|
|
81
|
+
"""
|
|
82
|
+
return (type(self), (self.source, self.destination, self.kind, self.occupant))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MetadataError(ComicboxError):
|
|
86
|
+
"""Metadata could not be loaded or routed through the source pipeline."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ExportError(ComicboxError):
|
|
90
|
+
"""A metadata file export failed its preconditions."""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class WriteValidationError(ComicboxError):
|
|
94
|
+
"""Raised when write_metadata inputs are inconsistent or invalid."""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class OnlineConfigurationError(ComicboxError):
|
|
98
|
+
"""Raised when OnlineSession inputs are inconsistent or incomplete."""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class OnlineLookupAbortedError(ComicboxError):
|
|
102
|
+
"""Raised when the selector callback returns ('abort', None)."""
|
|
@@ -32,9 +32,11 @@ class _ApiCounts:
|
|
|
32
32
|
|
|
33
33
|
Distinct from ``OnlineSource.api_call_counts``, which counts calls at
|
|
34
34
|
comicbox's wrapper level: that number includes response-cache hits and
|
|
35
|
-
counts a paginated result as one.
|
|
36
|
-
|
|
37
|
-
a user pastes into a bug report lines up with the server's
|
|
35
|
+
counts a paginated result as one. ``requests`` counts responses
|
|
36
|
+
RECEIVED, per endpoint, recorded where the raw response arrives, so
|
|
37
|
+
the number a user pastes into a bug report lines up with the server's
|
|
38
|
+
own logs. A send that never answered is counted only in
|
|
39
|
+
``connection_failures``.
|
|
38
40
|
"""
|
|
39
41
|
|
|
40
42
|
requests: dict[str, int] = field(default_factory=dict)
|
|
@@ -138,14 +140,16 @@ class _OutcomeStats:
|
|
|
138
140
|
self._per_source_api[source_name] = bucket
|
|
139
141
|
return bucket
|
|
140
142
|
|
|
141
|
-
def record_http_request(
|
|
142
|
-
|
|
143
|
-
) -> None:
|
|
144
|
-
"""Record one real HTTP send and what it waited at the rate gate."""
|
|
143
|
+
def record_http_request(self, source_name: str, endpoint: str) -> None:
|
|
144
|
+
"""Record one response received, by endpoint."""
|
|
145
145
|
with self._lock:
|
|
146
146
|
bucket = self._api_bucket_for(source_name)
|
|
147
147
|
bucket.requests[endpoint] = bucket.requests.get(endpoint, 0) + 1
|
|
148
|
-
|
|
148
|
+
|
|
149
|
+
def record_gate_wait(self, source_name: str, seconds: float) -> None:
|
|
150
|
+
"""Record what one send waited at the rate gate before going out."""
|
|
151
|
+
with self._lock:
|
|
152
|
+
self._api_bucket_for(source_name).blocked_seconds += seconds
|
|
149
153
|
|
|
150
154
|
def record_rate_limit_rejection(self, source_name: str) -> None:
|
|
151
155
|
"""Record one server rate-limit rejection (a 429 we still paid for)."""
|
|
@@ -379,6 +383,7 @@ record_skip = _STATS.record_skip
|
|
|
379
383
|
record_no_match = _STATS.record_no_match
|
|
380
384
|
record_explicit_id = _STATS.record_explicit_id
|
|
381
385
|
record_http_request = _STATS.record_http_request
|
|
386
|
+
record_gate_wait = _STATS.record_gate_wait
|
|
382
387
|
record_rate_limit_rejection = _STATS.record_rate_limit_rejection
|
|
383
388
|
record_unthrottled_response = _STATS.record_unthrottled_response
|
|
384
389
|
record_connection_failure = _STATS.record_connection_failure
|
|
@@ -65,6 +65,14 @@ class CandidateSummary:
|
|
|
65
65
|
# to reproduce client-side the precedence its old six-call cascade
|
|
66
66
|
# got from the order it made the calls in.
|
|
67
67
|
volume: int | None = None
|
|
68
|
+
# A genuinely larger image of the same cover, for a frontend that
|
|
69
|
+
# wants a hover or lightbox next to the thumbnail. Display only: the
|
|
70
|
+
# matcher keeps hashing `cover_url`, because hashing a larger image
|
|
71
|
+
# moves every score and would need a fresh calibration run (see
|
|
72
|
+
# `matcher.py`'s audit note). None means this record carries no
|
|
73
|
+
# larger tier than `cover_url` already is, which is what a frontend
|
|
74
|
+
# keys "no hover" on.
|
|
75
|
+
cover_url_full: str | None = None
|
|
68
76
|
|
|
69
77
|
|
|
70
78
|
@dataclass(frozen=True, slots=True)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Rate limits for online sources, for citation / audit.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
mokkari reads Metron's per-user limits off the `X-RateLimit-*` response
|
|
5
|
+
headers; since 4.8.0 it also takes an injectable `rate_limiter`, which
|
|
6
|
+
is where comicbox plugs its `RateGate` in (see
|
|
7
|
+
`comicbox/formats/metron_api/paced_session.py`). simyan builds its
|
|
8
|
+
ComicVine limiter internally with no injection point at all.
|
|
7
9
|
This module exists so the numbers are visible in our codebase (rather
|
|
8
10
|
than hidden in transitive dependencies) and can be cited / audited, and
|
|
9
11
|
so the consumers below have a stable constant to work from.
|