From adde7b1d9345065d91a3cbcd82ac6a4a6112cbd4 Mon Sep 17 00:00:00 2001 From: Christophe Prud'homme Date: Fri, 30 Oct 2026 11:51:51 +0100 Subject: [PATCH 1/3] feat: add LaTeX and Typst bundles with paper versioning Add configurable source archives, compiler backends, hashed manifests, and isolated compilation verification. Preserve Git provenance across both formats and prepare version 2.1.0. Closes #20 --- CHANGELOG.md | 11 + DEVELOPMENT.md | 37 ++ README.md | 93 ++++- docs/antora.yml | 4 +- docs/modules/ROOT/pages/configuration.adoc | 30 ++ docs/modules/ROOT/pages/lifecycle.adoc | 2 +- .../ROOT/pages/reference/commands.adoc | 37 ++ package-lock.json | 4 +- package.json | 2 +- pyproject.toml | 2 +- src/article_cli/__init__.py | 2 +- src/article_cli/cli.py | 1 + src/article_cli/commands/__init__.py | 2 + src/article_cli/commands/bundle.py | 93 +++++ src/article_cli/commands/version.py | 2 +- src/article_cli/config.py | 18 + src/article_cli/git_hooks.py | 3 +- src/article_cli/git_manager.py | 3 +- src/article_cli/paper_version.py | 108 ++++++ src/article_cli/repository_setup.py | 7 +- src/article_cli/services/__init__.py | 8 + src/article_cli/services/bundle.py | 327 +++++++++++++++++ src/article_cli/services/bundle_backends.py | 339 ++++++++++++++++++ src/article_cli/services/compiler.py | 15 +- src/article_cli/services/gitinfo.py | 50 ++- src/article_cli/services/release.py | 45 ++- src/article_cli/templates/article/ieee.tex.j2 | 4 +- src/article_cli/templates/article/lncs.tex.j2 | 4 +- src/article_cli/templates/article/lncs.typ.j2 | 3 + src/article_cli/templates/article/main.tex.j2 | 4 +- src/article_cli/templates/article/main.typ.j2 | 3 + .../templates/poster/poster.tex.j2 | 4 +- .../templates/poster/poster.typ.j2 | 3 + .../templates/presentation/beamer.tex.j2 | 4 +- .../templates/presentation/typst.typ.j2 | 3 + .../templates/project/README.md.j2 | 2 +- src/article_cli/typst_compiler.py | 9 + tests/golden/article_main.tex | 4 +- tests/test_bundle.py | 272 ++++++++++++++ tests/test_bundle_typst.py | 332 +++++++++++++++++ tests/test_cli.py | 11 +- tests/test_paper_version.py | 231 ++++++++++++ tests/test_release_service.py | 24 ++ tests/test_services.py | 6 +- uv.lock | 2 +- 45 files changed, 2114 insertions(+), 56 deletions(-) create mode 100644 src/article_cli/commands/bundle.py create mode 100644 src/article_cli/paper_version.py create mode 100644 src/article_cli/services/bundle.py create mode 100644 src/article_cli/services/bundle_backends.py create mode 100644 tests/test_bundle.py create mode 100644 tests/test_bundle_typst.py create mode 100644 tests/test_paper_version.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4301b4b..eaf4658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] - 2026-10-30 + +### Added +- `article-cli bundle` creates reproducible LaTeX and Typst source ZIPs through a shared service and injectable compiler strategies. LaTeX uses TeX recorder dependencies; Typst uses native JSON dependency reporting. +- Project include/exclude policy in `[tool.article-cli.bundle]`, repeatable CLI overrides, optional main PDF, dry-run file listing, and clean compilation verification. +- Typst bundle support includes imports, dynamic data, bibliography/styles, configured local fonts, and vendored external packages. +- Portable Typst Git snapshots with version/commit/branch/date/dirty state, integrated with `version`, `compile`, templates, and paper release workflows. LaTeX templates use local `gitinfo2` snapshots for archive portability. +- Adjacent JSON manifests record source hashes and the archive checksum; existing archives require `--force` to replace. + + ## [1.4.0] - 2026-01-18 ### Added @@ -220,6 +230,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `article-cli config show` - Show current configuration - `article-cli config create` - Create sample configuration +[2.1.0]: https://github.com/feelpp/article.cli/compare/v2.0.1...v2.1.0 [1.4.0]: https://github.com/feelpp/article.cli/releases/tag/v1.4.0 [1.3.2]: https://github.com/feelpp/article.cli/releases/tag/v1.3.2 [1.3.1]: https://github.com/feelpp/article.cli/releases/tag/v1.3.1 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index c5cc2e1..4cd79f9 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -217,3 +217,40 @@ When you need to update the tool: 2. Update version in `pyproject.toml` 3. Build and publish new version 4. Users update with: `uv tool upgrade article-cli` + + +## Source bundle API and compiler strategies + +`BundleService` orchestrates a shared workflow: resolve the document, select a +backend, build, collect required files, apply policy, optionally verify in a +clean directory, and publish a deterministic ZIP plus a manifest. + +```python +from article_cli.config import Config +from article_cli.services import BundleOptions, BundleService + +service = BundleService(Config("pyproject.toml")) +plan = service.plan(BundleOptions(document="main.typ", verify=True)) +archive = service.write(plan) +``` + +`BundleBackend` is a structural `Protocol` (Strategy pattern) with a source suffix +and two operations: `compile(BundleBuild)` and `dependencies(BundleBuild)`. +`LatexBundleBackend` and `TypstBundleBackend` encapsulate native compiler behavior; +the archive service has no source parser. `BundleBuild` and `BundleDependency` +are immutable data objects. A dependency carries its source, portable archive +name, and allowed source root; shared policy checks containment, exclusions, +collisions, and required-file availability for both backends. + +Callers can inject `BundleService(..., backends={engine: backend})` or a +`CommandRunner` for testing. The default registry maps the existing LaTeX engines +and `typst` to their strategies. Dependencies are collected from `.fls`/`.aux` (and Biber `.bcf`) +or Typst 0.14+ `--deps --deps-format json`; no regex-based Typst parser is used. +Typst package sources are vendored under `.article-cli/typst-packages` without +rewriting imports, and verification resolves fonts and packages from the staged +archive contents. System fonts and TeX distributions remain environment inputs. + +`GitSnapshot` in `paper_version.py` separates Git reads from document rendering. +Typst consumes safely serialized JSON; LaTeX uses the existing `gitinfo2` adapter. +`version` and `release` select the appropriate output format. Compiler services +pass absolute paths so the API works without changing the process directory. diff --git a/README.md b/README.md index 0106140..3dc8c8b 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ The `init` command sets up: ### Git Release Management ```bash -# Refresh gitinfo2 version metadata +# Refresh paper version metadata (LaTeX or Typst) article-cli version # Refresh metadata, compile, and check the PDF version text @@ -307,6 +307,97 @@ article-cli compile --clean-first --clean-after article-cli compile --output-dir build/ ``` +### Paper Version Metadata + +`article-cli version` writes LaTeX's `gitHeadLocal.gin` for `gitinfo2`, or a +portable `gitHeadLocal.json` snapshot beside a Typst main document. The Typst +snapshot contains the commit, short hash, branch, description, nearest tag, +commit date, and dirty flag. Generated metadata is excluded from dirty detection; +tracked source changes count as dirty, matching the existing LaTeX behavior. +`--dry-run` reports planned changes without writing files. + +```latex +\usepackage[local]{gitinfo2} +% In the document body: +Version: \gitDescribe{} / \gitAbbrevHash{} +``` + +```typst +#let git = json("gitHeadLocal.json") +#set page(footer: align(center)[Version: #git.description]) +``` + +New built-in templates include a version stamp. Typst compilation refreshes its +snapshot when Git is available; extracted source archives keep their frozen +snapshot. Typst reads JSON directly, so no extra Typst package is needed. + +```bash +article-cli version --document main.typ --compile --check-pdf --checksum +article-cli version --document main.tex --compile --check-pdf +``` + +### Submission Source Bundles (LaTeX and Typst) + +```bash +# Build and package the main document and its recorded dependencies +article-cli bundle main.tex --verify +article-cli bundle main.typ --verify + +# Preview an existing build without changing any files +article-cli bundle main.tex --dry-run + +# Extend project policy for one archive +article-cli bundle main.tex --include 'supplement/**' --exclude 'reviews/**' -o dist/preprint.zip +``` + +`bundle` automatically selects a LaTeX or Typst backend. LaTeX uses `latexmk` +and TeX recorder data; Typst 0.14+ uses native JSON dependency reporting, including +dynamically loaded data, imports, figures, and bibliography styles. The command +writes a reproducible ZIP and an adjacent JSON manifest with file hashes. +`--verify` recompiles only the selected files in a temporary directory before +publishing the archive. Both formats share the same selection and output policy. + +```toml +[tool.article-cli.bundle] +output = "dist/preprint.zip" +include = ["supplement/**"] +exclude = ["reviews/**", "local-only/**"] +include_pdf = false +verify = true +``` + +Paths and globs are relative to the main document's directory, which becomes the +ZIP root. Include patterns add files to detected dependencies; CLI patterns extend +those in configuration. Exclusions take precedence, and excluding a required +source fails with an explanation. `**` matches any number of directories. + +Git repository internals, virtual environments, `local-only`, `.env` files, and build logs are +excluded by default. The main PDF is excluded unless `--include-pdf` is set; figure +PDFs remain included. Keep all local dependencies and the build directory under +the main document's directory. System TeX packages are supplied by the receiving +service. LaTeX shell escape is disabled. + +Typst imports from external versioned packages are copied, with their manifests +and license files, under `.article-cli/typst-packages`. Configured local +`[tool.article-cli.typst].font_paths` must be under the document directory; font +files are included and their relative search paths recorded in the manifest. +System fonts remain environment dependencies. To compile an extracted Typst bundle: + +```bash +typst compile --root . --package-path .article-cli/typst-packages main.typ +# Add --font-path fonts when the bundle uses a configured local font directory. +``` + +Use `--build-dir build --no-build` to reuse existing LaTeX `.fls`/`.aux`/`.bbl` +data or Typst `.typst-deps.json` metadata, or `--force` to replace an +existing ZIP and manifest. Typst dependency metadata is generated by `bundle`; +a previous plain `compile` does not create it. Dry runs require existing metadata. `--no-verify` and `--no-include-pdf` override project policy. +Verification checks local compilability; HAL/arXiv still apply their own submission +and supported-format requirements. This command prepares files without uploading them. + +Run `article-cli version` before bundling when you need an up-to-date provenance +stamp. Bundling preserves the selected snapshot during compilation and verification. + ### Typst Compilation ```bash diff --git a/docs/antora.yml b/docs/antora.yml index de2fde6..9687c6b 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,6 +1,6 @@ name: article-cli title: Article CLI -version: '2.0' +version: '2.1' start_page: ROOT:index.adoc nav: - modules/ROOT/nav.adoc @@ -10,7 +10,7 @@ asciidoc: project-name: article-cli cli-command: article-cli python-supported: '3.9, 3.10, 3.11, 3.12, 3.13' - article-cli-version: '2.0.1' + article-cli-version: '2.1.0' template-version: '1' zotero-group-example: '4709047' page-pagination: '' diff --git a/docs/modules/ROOT/pages/configuration.adoc b/docs/modules/ROOT/pages/configuration.adoc index ccef2ab..702687c 100644 --- a/docs/modules/ROOT/pages/configuration.adoc +++ b/docs/modules/ROOT/pages/configuration.adoc @@ -57,6 +57,36 @@ build_dir = "build" font_paths = ["fonts"] ---- +== Submission Source Bundles + +[source,toml] +---- +[tool.article-cli.bundle] +output = "dist/preprint.zip" +include = ["supplement/**"] +exclude = ["reviews/**", "local-only/**"] +include_pdf = false +verify = true +---- + +The ZIP root and all patterns are relative to the main document directory. +`include` adds files to automatically detected dependencies. +Command-line `--include` and `--exclude` patterns extend configured lists. +Exclusions take precedence; excluding a required dependency is an error. +Globs support `*`, `?`, character classes, and recursive `**` (including zero directories). +Patterns cannot contain absolute paths or `..` components. + +The default policy excludes Git metadata, virtual environments, Python caches, +`local-only` directories, `.env` files, and build logs. +`include_pdf` controls the generated main PDF; required figure PDFs remain included. +`verify` recompiles the selected sources in isolation before writing output. +The same policy applies to `.tex` and `.typ` documents. +For Typst, `[tool.article-cli.typst].font_paths` directories must be under the +main document directory. Font files are included and search paths are recorded +in the manifest. Imported external packages are copied into the source archive. +Use `--no-verify` or `--no-include-pdf` for one-off overrides. +The same keys work in a standalone `.article-cli.toml` under `[bundle]`. + == Workflow [source,toml] diff --git a/docs/modules/ROOT/pages/lifecycle.adoc b/docs/modules/ROOT/pages/lifecycle.adoc index 4bd35c7..07ffa45 100644 --- a/docs/modules/ROOT/pages/lifecycle.adoc +++ b/docs/modules/ROOT/pages/lifecycle.adoc @@ -26,7 +26,7 @@ Build the configured document. The command reports the selected engine, output directory, version metadata, page count when `pdfinfo` is available, and common LaTeX warning counts. `version`:: -Refresh and report gitinfo2 metadata without creating a tag. +Refresh and report paper version metadata without creating a tag: `gitHeadLocal.gin` for LaTeX, `gitHeadLocal.json` for Typst. `release`:: Create a checked paper release. diff --git a/docs/modules/ROOT/pages/reference/commands.adoc b/docs/modules/ROOT/pages/reference/commands.adoc index aa0dffc..9f10264 100644 --- a/docs/modules/ROOT/pages/reference/commands.adoc +++ b/docs/modules/ROOT/pages/reference/commands.adoc @@ -49,12 +49,43 @@ article-cli compile --watch article-cli compile --clean-first --clean-after ---- +== Submission Source Bundles + +[source,console] +---- +article-cli bundle main.tex --verify +article-cli bundle main.typ --verify +article-cli bundle --dry-run +article-cli bundle --include 'supplement/**' --exclude 'reviews/**' +article-cli bundle --no-build --build-dir build -o dist/preprint.zip +article-cli bundle --include-pdf --force +---- + +The command selects a compiler backend and writes a source ZIP plus a JSON manifest. +LaTeX uses `latexmk` and TeX recorder data; Typst 0.14+ uses native JSON dependencies. +Typst imports, data files, figures, bibliography files/styles, configured local fonts, +and used external packages are collected automatically. +Used packages are copied under `.article-cli/typst-packages` with manifests and licenses. +`--verify` compiles the selected files in a temporary directory before writing the ZIP. +`--dry-run` lists files from existing build data without changing files. +`--no-build` reuses LaTeX recorder data or `.typst-deps.json` from a previous Typst bundle build. +Use `--force` to replace existing output. + +Paths are relative to the main document directory, which becomes the ZIP root. +Keep local dependencies and the build directory inside that directory. +Git repository internals, environments, private `local-only` files, and build logs are excluded. +Portable version snapshots are included when the document reads them. +The main PDF requires `--include-pdf`; figure PDFs are included as dependencies. +LaTeX shell escape is disabled. Local verification does not guarantee service acceptance. +See xref:configuration.adoc[configuration] for include/exclude policy. + == Version and Release [source,console] ---- article-cli version article-cli version --dry-run +article-cli version --document main.typ --compile --check-pdf article-cli release v1 --dry-run article-cli release v1 article-cli release v1 --bib check --push @@ -63,6 +94,12 @@ article-cli list --count 10 article-cli delete v1 ---- +The `version` command refreshes `gitinfo2`'s `gitHeadLocal.gin` for LaTeX or +`gitHeadLocal.json` for Typst. Use `\usepackage[local]{gitinfo2}` in LaTeX, +or `#let git = json("gitHeadLocal.json")` and `#git.description` in Typst. +The snapshot stays usable when Git is unavailable in an extracted bundle. +`version --dry-run` leaves metadata unchanged; `release` refreshes it before and after tagging. + == Assets [source,console] diff --git a/package-lock.json b/package-lock.json index 7551593..73bd725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "article-cli-docs", - "version": "2.0.1", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "article-cli-docs", - "version": "2.0.1", + "version": "2.1.0", "devDependencies": { "@antora/cli": "3.1.14", "@antora/site-generator": "3.1.14", diff --git a/package.json b/package.json index 03112ea..cccff18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "article-cli-docs", - "version": "2.0.1", + "version": "2.1.0", "private": true, "description": "Antora documentation site for article-cli", "scripts": { diff --git a/pyproject.toml b/pyproject.toml index b4257cd..2e8cf22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "article-cli" -version = "2.0.1" +version = "2.1.0" authors = [ {name = "Christophe Prud'homme", email = "prudhomm@cemosis.fr"}, ] diff --git a/src/article_cli/__init__.py b/src/article_cli/__init__.py index 4075a4d..0f767be 100644 --- a/src/article_cli/__init__.py +++ b/src/article_cli/__init__.py @@ -9,7 +9,7 @@ - Git hooks setup """ -__version__ = "2.0.1" +__version__ = "2.1.0" __author__ = "Christophe Prud'homme" __email__ = "prudhomm@cemosis.fr" diff --git a/src/article_cli/cli.py b/src/article_cli/cli.py index 0689ea9..c6e44ef 100644 --- a/src/article_cli/cli.py +++ b/src/article_cli/cli.py @@ -44,6 +44,7 @@ def create_parser() -> argparse.ArgumentParser: %(prog)s bib update --check # Check checked-in bibliography freshness %(prog)s bib update # Update references from Zotero %(prog)s compile # Compile configured main document + %(prog)s bundle --verify # Prepare a submission source ZIP %(prog)s version # Refresh and report git metadata %(prog)s version --compile --check-pdf # Refresh, compile, and inspect PDF text %(prog)s release v1 --dry-run # Preview release tag creation diff --git a/src/article_cli/commands/__init__.py b/src/article_cli/commands/__init__.py index 92ca893..a9f58bb 100644 --- a/src/article_cli/commands/__init__.py +++ b/src/article_cli/commands/__init__.py @@ -4,6 +4,7 @@ from . import ( bibtex, + bundle, clean, compile, config, @@ -25,6 +26,7 @@ version, release, bibtex, + bundle, config, fonts, themes, diff --git a/src/article_cli/commands/bundle.py b/src/article_cli/commands/bundle.py new file mode 100644 index 0000000..f001d46 --- /dev/null +++ b/src/article_cli/commands/bundle.py @@ -0,0 +1,93 @@ +"""Source archive command for HAL, arXiv, and other submission systems.""" + +import argparse +import subprocess + +from ..config import Config +from ..reporting import print_error, print_info, print_success +from ..services.bundle import BundleOptions, BundleService + + +def add_parser(subparsers): + parser = subparsers.add_parser("bundle", help="Create a LaTeX or Typst source ZIP") + parser.add_argument( + "document", nargs="?", help="Main .tex or .typ file (or configured document)" + ) + parser.add_argument( + "-o", "--output", help="ZIP path, relative to document directory" + ) + parser.add_argument( + "--build-dir", help="Build directory containing compiler dependency metadata" + ) + parser.add_argument( + "--include", + action="append", + default=[], + help="Add document-relative glob (repeatable)", + ) + parser.add_argument( + "--exclude", + action="append", + default=[], + help="Exclude document-relative glob (repeatable)", + ) + for key, help_text in [ + ("verify", "Recompile selected files in a clean directory"), + ("include-pdf", "Include the generated main PDF in the ZIP"), + ]: + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--" + key, + dest=key.replace("-", "_"), + action="store_true", + default=None, + help=help_text, + ) + group.add_argument( + "--no-" + key, dest=key.replace("-", "_"), action="store_false" + ) + parser.add_argument( + "--no-build", + action="store_true", + help="Use existing compiler dependency metadata and PDF", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="List files from existing build data; write nothing", + ) + parser.add_argument( + "--force", action="store_true", help="Replace an existing ZIP and manifest" + ) + parser.set_defaults(handler=run) + + +def run(args: argparse.Namespace, config: Config) -> int: + try: + service = BundleService(config) + plan = service.plan( + BundleOptions( + document=args.document, + output=args.output, + build_dir=args.build_dir, + include=tuple(args.include), + exclude=tuple(args.exclude), + include_pdf=args.include_pdf, + verify=args.verify, + no_build=args.no_build, + dry_run=args.dry_run, + ) + ) + print_info(f"Bundle files ({len(plan.files)}):") + for name in sorted(plan.files): + print(f" {name}") + if args.dry_run: + print_info(f"Would write: {plan.output}") + else: + output = service.write(plan, force=args.force) + print_success(f"Created source bundle: {output}") + print_info(f'Manifest: {output.with_suffix(".manifest.json")}') + return 0 + except (ValueError, OSError, subprocess.TimeoutExpired) as error: + print_error(str(error)) + return 1 diff --git a/src/article_cli/commands/version.py b/src/article_cli/commands/version.py index 288d89c..2ae5877 100644 --- a/src/article_cli/commands/version.py +++ b/src/article_cli/commands/version.py @@ -17,7 +17,7 @@ def add_parser(subparsers: Any) -> None: "version", help="Refresh and report git version metadata", description=( - "Refresh gitinfo2 metadata and report the current git version state " + "Refresh LaTeX gitinfo2 or Typst JSON metadata and report Git state " "without creating a tag." ), ) diff --git a/src/article_cli/config.py b/src/article_cli/config.py index a6036d9..3a4ccc1 100644 --- a/src/article_cli/config.py +++ b/src/article_cli/config.py @@ -205,6 +205,16 @@ def get_latex_config(self) -> Dict[str, Any]: "timeout": self.get("latex", "timeout", 300), } + def get_bundle_config(self) -> Dict[str, Any]: + """Get source archive policy; patterns are document-directory relative.""" + return { + "output": self.get("bundle", "output", ""), + "include": self.get("bundle", "include", []), + "exclude": self.get("bundle", "exclude", []), + "include_pdf": self.get("bundle", "include_pdf", False), + "verify": self.get("bundle", "verify", False), + } + def get_project_config(self) -> Dict[str, Any]: """Get project-level configuration""" return { @@ -426,6 +436,14 @@ def create_sample_config(self, path: Optional[Path] = None) -> Path: # Compilation timeout in seconds timeout = 300 +[bundle] +# Submission ZIP; paths and globs are relative to the main document directory. +# output = "dist/paper-sources.zip" +include = [] +exclude = ["local-only/**", "reviews/**"] +include_pdf = false +verify = false + [project] # Project type: "article", "typst-article", "presentation", "typst-presentation", # "poster", or "typst-poster" diff --git a/src/article_cli/git_hooks.py b/src/article_cli/git_hooks.py index 49c5505..e699d99 100644 --- a/src/article_cli/git_hooks.py +++ b/src/article_cli/git_hooks.py @@ -10,6 +10,7 @@ from pathlib import Path from typing import List, Optional +from .paper_version import METADATA_EXCLUDES from .reporting import print_info, print_success, print_warning MANAGED_HOOK_START = "# >>> article-cli gitinfo2 hook >>>" @@ -279,7 +280,7 @@ def _is_dirty_for_gitinfo2(repo_path: Path) -> bool: "--untracked-files=no", "--", ".", - ":(exclude)gitHeadLocal.gin", + *METADATA_EXCLUDES, ], ) return bool(result and result.stdout.strip()) diff --git a/src/article_cli/git_manager.py b/src/article_cli/git_manager.py index 863346c..c4b775c 100644 --- a/src/article_cli/git_manager.py +++ b/src/article_cli/git_manager.py @@ -16,6 +16,7 @@ install_managed_gitinfo2_hook, refresh_gitinfo2_metadata, ) +from .paper_version import METADATA_EXCLUDES from .reporting import Colors, print_error, print_info, print_success, print_warning @@ -294,7 +295,7 @@ def dirty_files(self, ignore_gitinfo: bool = True) -> List[str]: """Return dirty tracked/untracked files, optionally ignoring gitHeadLocal.gin.""" pathspec = ["--", "."] if ignore_gitinfo: - pathspec.append(":(exclude)gitHeadLocal.gin") + pathspec.extend(METADATA_EXCLUDES) result = self.git(["status", "--porcelain", *pathspec]) if result.returncode != 0: return [""] diff --git a/src/article_cli/paper_version.py b/src/article_cli/paper_version.py new file mode 100644 index 0000000..997f8b0 --- /dev/null +++ b/src/article_cli/paper_version.py @@ -0,0 +1,108 @@ +"""Portable paper provenance: read Git once and export a Typst JSON snapshot. + +Git access is outside the document compiler. Archived documents retain the frozen +snapshot, while working repositories refresh it through article-cli. LaTeX keeps +its established gitinfo2 adapter in git_hooks. +""" + +import json +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Optional + +from .command_runner import DEFAULT_RUNNER, CommandRunner + +TYPST_METADATA_FILE = "gitHeadLocal.json" +# Generated provenance must not make the next snapshot dirty by itself. +METADATA_EXCLUDES = ( + ":(exclude)gitHeadLocal.gin", + ":(glob,exclude)**/gitHeadLocal.gin", + ":(exclude)gitHeadLocal.json", + ":(glob,exclude)**/gitHeadLocal.json", +) + + +@dataclass(frozen=True) +class GitSnapshot: + """Format-neutral, deterministic paper version state (no build timestamp).""" + + commit: str = "" + short_commit: str = "" + branch: str = "unversioned" + description: str = "unversioned" + release: str = "" + commit_date: str = "" + dirty: bool = False + schema_version: int = 1 + + +def read_git_snapshot( + start_path: Path, runner: CommandRunner = DEFAULT_RUNNER +) -> Optional[GitSnapshot]: + """Read an existing commit; tracked source edits define the dirty flag.""" + + def git(*args): + try: + result = runner.run(["git", *args], cwd=start_path) + return result.stdout.strip() if result.returncode == 0 else None + except (FileNotFoundError, OSError): + return None + + fields = git("log", "-1", "--format=%H%n%h%n%cI", "HEAD") + if not fields: + return None + values = fields.splitlines() + if len(values) != 3: + return None + # Query at the Git root so changes in sibling documents are visible too. + root = git("rev-parse", "--show-toplevel") + if not root: + return None + result = runner.run( + [ + "git", + "status", + "--porcelain", + "--untracked-files=no", + "--", + ".", + *METADATA_EXCLUDES, + ], + cwd=Path(root), + ) + if result.returncode: + return None + dirty = bool(result.stdout.strip()) + description = git("describe", "--tags", "--long", "--always") or values[1] + release = git("describe", "--tags", "--abbrev=0") or "" + return GitSnapshot( + commit=values[0], + short_commit=values[1], + commit_date=values[2], + branch=git("symbolic-ref", "--quiet", "--short", "HEAD") or "detached", + description=description + ("-*" if dirty else ""), + release=release, + dirty=dirty, + ) + + +def write_typst_snapshot(snapshot: GitSnapshot, directory: Path) -> Path: + """Write a stable, safely escaped snapshot beside the main document.""" + path = directory / TYPST_METADATA_FILE + content = ( + json.dumps(asdict(snapshot), indent=2, sort_keys=True, ensure_ascii=False) + + "\n" + ) + if not path.is_file() or path.read_text() != content: + path.write_text(content) + return path + + +def refresh_typst_metadata( + directory: Path, dry_run: bool = False +) -> Optional[GitSnapshot]: + """Refresh when Git is available; preserve an archive's existing snapshot.""" + snapshot = read_git_snapshot(directory) + if snapshot is not None and not dry_run: + write_typst_snapshot(snapshot, directory) + return snapshot diff --git a/src/article_cli/repository_setup.py b/src/article_cli/repository_setup.py index aff8cc3..3ec3a4d 100644 --- a/src/article_cli/repository_setup.py +++ b/src/article_cli/repository_setup.py @@ -14,10 +14,11 @@ from typing import List, Optional, Sequence, Union from .git_hooks import ensure_gitinfo2_hook_source +from .paper_version import GitSnapshot, read_git_snapshot, write_typst_snapshot from .template_renderer import TEMPLATE_VERSION, TemplateRenderer from .reporting import print_error, print_info, print_success -ARTICLE_CLI_MIN_VERSION = "2.0.1" +ARTICLE_CLI_MIN_VERSION = "2.1.0" ARTICLE_TEMPLATES = { ("article", "default"): "article/main.tex.j2", ("article", "lncs"): "article/lncs.tex.j2", @@ -422,6 +423,10 @@ def _create_tex_file( ) doc_path.write_text(doc_content, encoding="utf-8") + if is_typst and not (doc_path.parent / "gitHeadLocal.json").exists(): + write_typst_snapshot( + read_git_snapshot(self.repo_path) or GitSnapshot(), doc_path.parent + ) print_success(f"Created: {doc_path.relative_to(self.repo_path)}") return True diff --git a/src/article_cli/services/__init__.py b/src/article_cli/services/__init__.py index 7000d26..5191b05 100644 --- a/src/article_cli/services/__init__.py +++ b/src/article_cli/services/__init__.py @@ -3,6 +3,8 @@ """ from .bibliography import BibliographyService, BibliographyUpdateOptions +from .bundle import BundleOptions, BundlePlan, BundleService +from .bundle_backends import BundleBackend, BundleBuild, BundleDependency from .compiler import CompileOptions, CompilerService from .git import GitService from .gitinfo import GitInfoService @@ -12,6 +14,12 @@ __all__ = [ "BibliographyService", "BibliographyUpdateOptions", + "BundleOptions", + "BundlePlan", + "BundleService", + "BundleBackend", + "BundleBuild", + "BundleDependency", "CompileOptions", "CompilerService", "GitInfoService", diff --git a/src/article_cli/services/bundle.py b/src/article_cli/services/bundle.py new file mode 100644 index 0000000..51cc35a --- /dev/null +++ b/src/article_cli/services/bundle.py @@ -0,0 +1,327 @@ +"""Shared source selection, verification, and archive publication policy.""" + +import fnmatch +import hashlib +import json +import os +import tempfile +import zipfile +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import Dict, List, Mapping, Optional, Set, Tuple + +from .. import __version__ +from ..command_runner import DEFAULT_RUNNER, CommandRunner +from ..config import Config +from ..project_context import ProjectContext +from .bundle_backends import ( + BundleBackend, + BundleBuild, + BundleDependency, + TEX_JUNK_SUFFIXES, + default_bundle_backends, + inside, +) + +DEFAULT_EXCLUDES = [ + ".git", + "**/.git", + ".git/**", + "**/.git/**", + ".venv/**", + "**/.venv/**", + "__pycache__/**", + "**/__pycache__/**", + "**/local-only/**", + "**/.env", + "**/.env.*", +] + + +@dataclass(frozen=True) +class BundleOptions: + """Explicit command-line overrides; lists extend project patterns.""" + + document: Optional[str] = None + output: Optional[str] = None + build_dir: Optional[str] = None + include: Tuple[str, ...] = () + exclude: Tuple[str, ...] = () + include_pdf: Optional[bool] = None + verify: Optional[bool] = None + no_build: bool = False + dry_run: bool = False + + +@dataclass +class BundlePlan: + """Selected source paths keyed by their names in the archive.""" + + root: Path + document: str + output: Path + files: Dict[str, Path] + engine: str + verify: bool + build_dir: Path + timeout: int + font_paths: Tuple[str, ...] = () + + def manifest(self, payloads: Dict[str, bytes]) -> Dict: + """Return portable, deterministic provenance without absolute paths.""" + return { + "article_cli_version": __version__, + "document": self.document, + "engine": self.engine, + "font_paths": list(self.font_paths), + "files": [ + { + "path": name, + "size": len(data), + "sha256": hashlib.sha256(data).hexdigest(), + } + for name, data in payloads.items() + ], + } + + +def _pattern(value: str) -> str: + if ( + not value + or "\\" in value + or Path(value).is_absolute() + or ".." in PurePosixPath(value).parts + or ":" in value + ): + raise ValueError( + f"Bundle patterns must stay inside the document directory: {value!r}" + ) + return value.removeprefix("./") + + +def _matches(name: str, pattern: str) -> bool: + """Match root-relative globs, including zero-directory ** matches.""" + names = PurePosixPath(name).parts + patterns = PurePosixPath(pattern).parts + + def match(i: int, j: int) -> bool: + if j == len(patterns): + return i == len(names) + if patterns[j] == "**": + return match(i, j + 1) or (i < len(names) and match(i + 1, j)) + return ( + i < len(names) + and fnmatch.fnmatchcase(names[i], patterns[j]) + and match(i + 1, j + 1) + ) + + return match(0, 0) + + +class BundleService: + """Collect dependencies, apply project policy, verify, and write a ZIP.""" + + def __init__( + self, + config: Config, + cwd: Optional[Path] = None, + runner: CommandRunner = DEFAULT_RUNNER, + backends: Optional[Mapping[str, BundleBackend]] = None, + ): + self.config = config + self.cwd = (cwd or Path.cwd()).resolve() + self.backends = ( + default_bundle_backends(runner) if backends is None else backends + ) + + def plan(self, options: BundleOptions) -> BundlePlan: + policy = self.config.get_bundle_config() + context = ProjectContext.resolve( + self.config, cwd=self.cwd, document=options.document + ) + document = context.document + if document is None or not document.is_file(): + raise ValueError("Bundle requires an existing main .tex or .typ document.") + backend = self.backends.get(context.engine) + if backend is None or document.suffix != backend.suffix: + raise ValueError( + f"Unsupported bundle document/engine: {document.suffix}/{context.engine}" + ) + # Root the ZIP at the main document directory, as archive services compile there. + root = document.parent.resolve() + build_value = ( + options.build_dir or context.output_dir_name or ".article-cli/bundle-build" + ) + build_dir = (root / build_value).resolve() + if not inside(build_dir, root): + raise ValueError( + "Bundle build directory must stay inside the document directory." + ) + output_value = ( + options.output or policy["output"] or f"dist/{document.stem}-sources.zip" + ) + output = (root / output_value).resolve() + if output.suffix.lower() != ".zip": + raise ValueError("Bundle output must have a .zip extension.") + timeout = int(self.config.get_latex_config().get("timeout", 300)) + include = self._patterns(policy["include"], "include") + list(options.include) + exclude = ( + DEFAULT_EXCLUDES + + self._patterns(policy["exclude"], "exclude") + + list(options.exclude) + ) + include = [_pattern(p) for p in include] + exclude = [ + _pattern(p.rstrip("/") + "/**" if (root / p).is_dir() else p) + for p in exclude + ] + include_pdf = ( + options.include_pdf + if options.include_pdf is not None + else policy["include_pdf"] + ) + verify = options.verify if options.verify is not None else policy["verify"] + if not isinstance(include_pdf, bool) or not isinstance(verify, bool): + raise ValueError("bundle.include_pdf and bundle.verify must be booleans.") + font_paths = self._font_paths(root) if context.is_typst else () + build = BundleBuild(document, build_dir, timeout, font_paths) + if not options.no_build and not options.dry_run: + backend.compile(build) + files: Dict[str, Path] = {} + required: Set[str] = set() + main_pdf = build_dir / document.with_suffix(".pdf").name + reserved = {output, output.with_suffix(".manifest.json")} + + def add(dependency: BundleDependency, needed: bool = False) -> None: + path = dependency.source + name = _pattern(dependency.name) + resolved = path.resolve() + if resolved in reserved or ( + resolved == main_pdf.resolve() and not include_pdf + ): + return + if not inside(resolved, dependency.source_root.resolve()): + raise ValueError( + f"Cannot bundle a dependency outside the document directory: {path}" + ) + blocked = any(_matches(name, p) for p in exclude) + # Backends have already removed their own compiler artifacts. + # Required inputs may legitimately use a log-like suffix (e.g. read("data.log")). + junk = not needed and ( + path.suffix in TEX_JUNK_SUFFIXES + or name.endswith((".synctex.gz", ".typst-deps.json", ".run.xml")) + ) + if blocked or junk: + if needed: + raise ValueError( + f"Required dependency excluded from bundle: {name}" + ) + return + if not path.is_file(): + raise ValueError(f"Missing bundle dependency: {path}") + if name in files and files[name].resolve() != resolved: + raise ValueError( + f"Two dependencies map to the same archive path: {name}" + ) + files[name] = path + if needed: + required.add(name) + + add(BundleDependency(document, document.name, root), needed=True) + for dependency in backend.dependencies(build): + add(dependency, needed=True) + for pattern in include: + matches = list(root.glob(pattern)) + if not matches: + raise ValueError(f"Include pattern matched no files: {pattern}") + for match in matches: + paths = match.rglob("*") if match.is_dir() else [match] + for path in paths: + if path.is_file(): + add( + BundleDependency( + path, path.relative_to(root).as_posix(), root + ) + ) + if include_pdf: + add(BundleDependency(main_pdf, main_pdf.name, root)) + if not files or document.name not in required: + raise ValueError("Bundle must include its main document.") + return BundlePlan( + root, + document.name, + output, + files, + context.engine, + bool(verify), + build_dir, + timeout, + font_paths, + ) + + def _font_paths(self, root: Path) -> Tuple[str, ...]: + values = self._patterns( + self.config.get_typst_config()["font_paths"], "font_paths" + ) + result = [] + for value in values: + path = (root / value).resolve() + if not inside(path, root): + raise ValueError( + f"Typst font directories must stay inside the document directory: {value}" + ) + if not path.is_dir(): + raise ValueError(f"Missing Typst font directory: {value}") + result.append(path.relative_to(root).as_posix()) + return tuple(result) + + @staticmethod + def _patterns(value: object, key: str) -> List[str]: + if not isinstance(value, list) or any(not isinstance(p, str) for p in value): + raise ValueError(f"bundle.{key} must be a list of glob strings.") + return value + + def write(self, plan: BundlePlan, force: bool = False) -> Path: + """Publish the ZIP only after its selected sources pass requested verification.""" + manifest_path = plan.output.with_suffix(".manifest.json") + if not force and (plan.output.exists() or manifest_path.exists()): + raise ValueError("Bundle output already exists; use --force to replace it.") + payloads = { + name: path.read_bytes() for name, path in sorted(plan.files.items()) + } + if plan.verify: + with tempfile.TemporaryDirectory(prefix="article-cli-bundle-") as directory: + stage = Path(directory) + for name, data in payloads.items(): + dest = stage / name + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(data) + self.backends[plan.engine].compile( + BundleBuild( + stage / plan.document, + stage / ".verify-build", + plan.timeout, + plan.font_paths, + ) + ) + plan.output.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(dir=plan.output.parent) as directory: + archive = Path(directory) / "bundle.zip" + with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as z: + for name, data in payloads.items(): + info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + info.compress_type = zipfile.ZIP_DEFLATED + info.external_attr = 0o100644 << 16 + z.writestr(info, data) + manifest = plan.manifest(payloads) + manifest["verified"] = plan.verify + manifest["archive_sha256"] = hashlib.sha256( + archive.read_bytes() + ).hexdigest() + staged_manifest = Path(directory) / "manifest.json" + staged_manifest.write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n" + ) + os.replace(archive, plan.output) + os.replace(staged_manifest, manifest_path) + return plan.output diff --git a/src/article_cli/services/bundle_backends.py b/src/article_cli/services/bundle_backends.py new file mode 100644 index 0000000..a2a90fc --- /dev/null +++ b/src/article_cli/services/bundle_backends.py @@ -0,0 +1,339 @@ +"""Compiler strategies for source bundles, independent of archive policy. + +A backend compiles a build request and reports required files with portable ZIP +names. BundleService owns selection policy, verification, and publication. New +backends can implement the protocol without inheriting a service implementation. +""" + +import json +import os +import re +from xml.etree import ElementTree +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Mapping, Optional, Protocol, Tuple + +from ..command_runner import CommandRunner +from ..config import tomllib +from ..project_context import LATEX_ENGINES + +TYPST_PACKAGES = ".article-cli/typst-packages" +TEX_JUNK_SUFFIXES = { + ".aux", + ".log", + ".out", + ".fls", + ".blg", + ".bcf", + ".toc", + ".lof", + ".lot", + ".fdb_latexmk", + ".cut", + ".synctex", +} +GENERATED_INPUTS = {".bbl", ".ind", ".gls", ".nls"} +FONT_SUFFIXES = {".otf", ".ttf", ".otc", ".ttc"} + + +def inside(path: Path, root: Path) -> bool: + """Whether an absolute path is contained in an absolute root.""" + try: + path.relative_to(root) + return True + except ValueError: + return False + + +@dataclass(frozen=True) +class BundleBuild: + """Compiler input; font paths are relative to the document directory.""" + + document: Path + build_dir: Path + timeout: int = 300 + font_paths: Tuple[str, ...] = () + + @property + def root(self) -> Path: + return self.document.parent + + @property + def pdf(self) -> Path: + return self.build_dir / self.document.with_suffix(".pdf").name + + +@dataclass(frozen=True) +class BundleDependency: + """A source mapped into the ZIP, bounded by its approved source root. + + Ordinarily source_root is the document directory. A backend can explicitly + vendor an external package under a portable name using the package's root. + The shared service still checks containment and prevents ZIP path escapes. + """ + + source: Path + name: str + source_root: Path + + +class BundleBackend(Protocol): + """Strategy boundary for engine-specific build and dependency discovery.""" + + suffix: str + + def compile(self, build: BundleBuild) -> None: + """Build the PDF and dependency metadata, raising on failure.""" + ... + + def dependencies(self, build: BundleBuild) -> Iterable[BundleDependency]: + """Read existing compiler metadata without changing files.""" + ... + + +class LatexBundleBackend: + """Use latexmk recorder output and BibTeX auxiliary data.""" + + suffix = ".tex" + + def __init__(self, engine: str, runner: CommandRunner): + self.engine = engine + self.runner = runner + + def compile(self, build: BundleBuild) -> None: + mode = {"xelatex": "-xelatex", "lualatex": "-lualatex"}.get(self.engine, "-pdf") + build.build_dir.mkdir(parents=True, exist_ok=True) + result = self.runner.run( + [ + "latexmk", + mode, + "-recorder", + "-no-shell-escape", + "-halt-on-error", + "-interaction=nonstopmode", + "-outdir=" + str(build.build_dir), + build.document.name, + ], + cwd=build.root, + timeout=build.timeout, + ) + if result.returncode: + raise ValueError( + "Bundle compilation failed:\n" + (result.stdout + result.stderr)[-4000:] + ) + log = build.build_dir / build.document.with_suffix(".log").name + if log.is_file() and re.search( + r"There were undefined (?:references|citations)|(?:Citation|Reference)[^\n]*undefined", + log.read_text(errors="replace"), + ): + raise ValueError( + "Bundle compilation has unresolved citations or references." + ) + if not build.pdf.is_file(): + raise ValueError( + "Bundle compilation reported success but PDF was not produced." + ) + + def dependencies(self, build: BundleBuild) -> Iterable[BundleDependency]: + recorder = build.build_dir / build.document.with_suffix(".fls").name + if not recorder.is_file(): + raise ValueError( + f"No TeX recorder file: {recorder}. Build first, or supply --build-dir." + ) + pwd = build.root + for line in recorder.read_text(errors="replace").splitlines(): + if line.startswith("PWD "): + pwd = Path(line[4:].strip()).resolve() + elif line.startswith("INPUT "): + raw = Path(line[6:].strip().strip('"')) + lexical = Path(os.path.abspath(pwd / raw)) + path = lexical.resolve() + if not inside(lexical, build.root): + if not raw.is_absolute(): + raise ValueError( + f"Dependency outside the document directory: {raw}" + ) + continue # The receiving environment supplies system TeX files. + if path == build.pdf.resolve(): + continue + if path.suffix in TEX_JUNK_SUFFIXES or path.name.endswith( + (".synctex.gz", ".run.xml") + ): + continue + generated = ( + path.parent == build.build_dir and path.suffix in GENERATED_INPUTS + ) + name = ( + path.name + if generated + else lexical.relative_to(build.root).as_posix() + ) + yield BundleDependency(lexical, name, build.root) + # BibTeX input/style dependencies are not necessarily present in .fls. + aux = build.build_dir / build.document.with_suffix(".aux").name + if aux.is_file(): + data = aux.read_text(errors="replace") + for command, suffix in [("bibdata", ".bib"), ("bibstyle", ".bst")]: + for group in re.findall(r"\\" + command + r"\{([^}]+)\}", data): + for value in group.split(","): + raw = Path(value.strip()) + if not raw.suffix: + raw = raw.with_suffix(suffix) + candidate = build.root / raw + if candidate.is_file() or suffix == ".bib": + yield BundleDependency( + candidate, raw.as_posix(), build.root + ) + if r"\bibdata" in data: + bbl = build.build_dir / build.document.with_suffix(".bbl").name + yield BundleDependency(bbl, bbl.name, build.root) + + # Biber reports bibliography sources in XML, rather than \bibdata in .aux. + bcf = build.build_dir / build.document.with_suffix(".bcf").name + if bcf.is_file(): + try: + tree = ElementTree.parse(bcf) + except ElementTree.ParseError as error: + raise ValueError(f"Invalid Biber dependency metadata: {bcf}") from error + for node in tree.iter(): + if node.tag.rsplit("}", 1)[-1] == "datasource" and node.text: + raw = Path(node.text.strip()) + yield BundleDependency(build.root / raw, raw.as_posix(), build.root) + bbl = build.build_dir / build.document.with_suffix(".bbl").name + yield BundleDependency(bbl, bbl.name, build.root) + + +class TypstBundleBackend: + """Use native JSON dependency reporting (Typst 0.14+) without parsing Typst.""" + + suffix = ".typ" + + def __init__(self, runner: CommandRunner): + self.runner = runner + + @staticmethod + def recorder(build: BundleBuild) -> Path: + return build.build_dir / (build.document.stem + ".typst-deps.json") + + def compile(self, build: BundleBuild) -> None: + build.build_dir.mkdir(parents=True, exist_ok=True) + command = [ + "typst", + "compile", + "--root", + str(build.root), + "--deps", + str(self.recorder(build)), + "--deps-format", + "json", + "--package-path", + str(build.root / TYPST_PACKAGES), + ] + for name in build.font_paths: + command.extend(["--font-path", str(build.root / name)]) + command.extend([build.document.name, str(build.pdf)]) + result = self.runner.run(command, cwd=build.root, timeout=build.timeout) + if result.returncode: + details = (result.stdout + result.stderr)[-4000:] + raise ValueError( + "Typst bundle compilation failed (requires Typst 0.14+):\n" + details + ) + if not build.pdf.is_file() or not self.recorder(build).is_file(): + raise ValueError( + "Typst compilation did not produce PDF and dependency metadata." + ) + + def dependencies(self, build: BundleBuild) -> Iterable[BundleDependency]: + recorder = self.recorder(build) + if not recorder.is_file(): + raise ValueError( + f"No Typst dependency file: {recorder}. Build first, or supply --build-dir." + ) + try: + data = json.loads(recorder.read_text()) + except (ValueError, UnicodeError) as error: + raise ValueError( + f"Invalid Typst dependency metadata: {recorder}" + ) from error + inputs = data.get("inputs") if isinstance(data, dict) else None + if not isinstance(inputs, list) or any( + not isinstance(p, str) or not p for p in inputs + ): + raise ValueError( + "Typst dependency metadata must contain an inputs list of paths." + ) + packages = set() + for value in inputs: + # Native paths are relative to the compiler's cwd, not the JSON file. + source = Path(os.path.abspath(build.root / value)) + if inside(source, build.root): + yield BundleDependency( + source, source.relative_to(build.root).as_posix(), build.root + ) + continue + package_root = self._package_root(source) + if package_root is None: + raise ValueError( + f"Typst dependency outside the document directory is not a package: {source}" + ) + namespace = package_root.parent.parent.name + prefix = f"{TYPST_PACKAGES}/{namespace}/{package_root.parent.name}/{package_root.name}" + yield BundleDependency( + source, + f"{prefix}/{source.relative_to(package_root).as_posix()}", + package_root, + ) + if package_root not in packages: + packages.add(package_root) + for path in sorted(package_root.iterdir()): + if path.is_file() and ( + path.name == "typst.toml" + or path.name.lower().startswith( + ("license", "licence", "copying") + ) + ): + yield BundleDependency( + path, f"{prefix}/{path.name}", package_root + ) + # Typst's dependency report does not list font files searched via --font-path. + for name in build.font_paths: + directory = build.root / name + if not directory.is_dir(): + raise ValueError(f"Missing Typst font directory: {name}") + for path in sorted(directory.rglob("*")): + if path.is_file() and path.suffix.lower() in FONT_SUFFIXES: + yield BundleDependency( + path, path.relative_to(build.root).as_posix(), build.root + ) + + @staticmethod + def _package_root(source: Path) -> Optional[Path]: + """Recognize a versioned package using its manifest and directory layout.""" + for parent in source.parents: + manifest = parent / "typst.toml" + if not manifest.is_file(): + continue + try: + if tomllib is None: + raise ValueError("TOML support is required for Typst packages.") + data = tomllib.loads(manifest.read_text()) + package = data.get("package", {}) + if ( + isinstance(package, dict) + and package.get("name") == parent.parent.name + and package.get("version") == parent.name + and re.fullmatch(r"[a-zA-Z0-9_-]+", parent.parent.parent.name) + ): + return parent + except (ValueError, UnicodeError): + continue + return None + + +def default_bundle_backends(runner: CommandRunner) -> Mapping[str, BundleBackend]: + """Create the default strategy registry with one subprocess boundary.""" + backends: dict[str, BundleBackend] = { + engine: LatexBundleBackend(engine, runner) for engine in LATEX_ENGINES + } + backends["typst"] = TypstBundleBackend(runner) + return backends diff --git a/src/article_cli/services/compiler.py b/src/article_cli/services/compiler.py index 362138b..4b11a49 100644 --- a/src/article_cli/services/compiler.py +++ b/src/article_cli/services/compiler.py @@ -85,6 +85,13 @@ def compile(self, options: CompileOptions) -> bool: return False return self._compile_latex(context, options) + @staticmethod + def _output_directory(context: ProjectContext) -> Optional[str]: + """Pass absolute paths to compilers so service cwd never needs os.chdir.""" + if context.output_dir is None: + return None + return str((context.project_root / context.output_dir).resolve()) + def _compile_typst( self, context: ProjectContext, @@ -94,8 +101,8 @@ def _compile_typst( compiler = self.typst_compiler_cls(self.config) return bool( compiler.compile( - typ_file=context.document_name or str(context.document), - output_dir=context.output_dir_name, + typ_file=str(context.document), + output_dir=self._output_directory(context), font_paths=options.font_paths, watch=options.watch, ) @@ -115,10 +122,10 @@ def _compile_latex( compiler = self.latex_compiler_cls(self.config) success = bool( compiler.compile( - tex_file=context.document_name or str(context.document), + tex_file=str(context.document), engine=context.engine, shell_escape=context.shell_escape, - output_dir=context.output_dir_name, + output_dir=self._output_directory(context), watch=options.watch, ) ) diff --git a/src/article_cli/services/gitinfo.py b/src/article_cli/services/gitinfo.py index 44df00e..92ed2d7 100644 --- a/src/article_cli/services/gitinfo.py +++ b/src/article_cli/services/gitinfo.py @@ -1,5 +1,5 @@ """ -gitinfo2 metadata service. +Paper version metadata service for LaTeX and Typst. """ from dataclasses import dataclass @@ -10,6 +10,7 @@ from ..config import Config from ..git_manager import GitManager from ..project_context import ProjectContext +from ..paper_version import refresh_typst_metadata, read_git_snapshot from ..reporting import print_error, print_info, print_success from .compiler import CompileOptions, CompilerService from .release import _absolute_pdf_path, pdf_contains_text, write_sha256 @@ -17,7 +18,7 @@ @dataclass(frozen=True) class VersionOptions: - """User-facing gitinfo2 version workflow options.""" + """User-facing paper version workflow options.""" dry_run: bool = False compile_pdf: bool = False @@ -31,7 +32,7 @@ class VersionOptions: class GitInfoService: - """Service boundary for gitinfo2 metadata refresh/reporting.""" + """Resolve document format, refresh provenance, and validate versioned PDFs.""" def __init__( self, @@ -53,8 +54,42 @@ def refresh(self, options: Union[bool, VersionOptions] = False) -> bool: if isinstance(options, bool): return self.manager.refresh_version_metadata(options) - if not self.manager.refresh_version_metadata(options.dry_run): + context = ProjectContext.resolve( + self.config, + cwd=self.manager.repo_root, + document=options.document, + engine=options.engine, + output_dir=options.output_dir, + ) + if options.compile_pdf or options.check_pdf or options.checksum: + if context.document is None or not context.document.is_file(): + print_error( + f"No paper document found in {self.manager.repo_root}. " + "Run from the paper repository or select its main file with --document. " + "No version metadata was changed." + ) + return False + snapshot = None + if context.is_typst and context.document is not None: + snapshot = refresh_typst_metadata(context.document.parent, options.dry_run) + if snapshot is None: + print_error( + "Paper version metadata requires a Git repository with a commit." + ) + return False + print_info(f"Git version: {snapshot.description}; branch {snapshot.branch}") + if options.dry_run: + print_info("Would refresh gitHeadLocal.json beside the Typst document.") + else: + print_success("Refreshed gitHeadLocal.json") + elif not self.manager.refresh_version_metadata(options.dry_run): return False + elif not options.dry_run and context.document is not None: + # gitinfo2's local mode also works when the main source is nested. + source = self.manager.repo_root / "gitHeadLocal.gin" + target = context.document.parent / "gitHeadLocal.gin" + if source.is_file() and source != target: + target.write_bytes(source.read_bytes()) if options.dry_run: self._print_dry_run(options) @@ -75,7 +110,9 @@ def refresh(self, options: Union[bool, VersionOptions] = False) -> bool: return False if options.check_pdf: - expected_text = options.tag or self._current_description() + expected_text = options.tag or ( + snapshot.description if snapshot else self._current_description() + ) if not expected_text: print_error("Could not determine a version string for PDF checking.") return False @@ -117,6 +154,9 @@ def _expected_pdf_path(self, options: VersionOptions) -> Path: def _current_description(self) -> Optional[str]: """Return the current git describe string.""" + snapshot = read_git_snapshot(self.manager.repo_root) + if snapshot is not None: + return snapshot.description result = self.manager.git( ["describe", "--tags", "--long", "--always", "--dirty=-*"] ) diff --git a/src/article_cli/services/release.py b/src/article_cli/services/release.py index 43c14a2..6ca5ddc 100644 --- a/src/article_cli/services/release.py +++ b/src/article_cli/services/release.py @@ -16,6 +16,7 @@ from ..git_hooks import gitinfo2_metadata_summary, refresh_gitinfo2_metadata from ..git_manager import GitManager from ..project_context import ProjectContext +from ..paper_version import refresh_typst_metadata, TYPST_METADATA_FILE from ..reporting import print_error, print_info, print_success, print_warning from .bibliography import BibliographyService, BibliographyUpdateOptions from .compiler import CompileOptions, CompilerService @@ -140,10 +141,10 @@ def release(self, options: ReleaseOptions) -> bool: if not self._run_bibliography(resolved.bibliography): return False - if not self._refresh_metadata(): + if not self._refresh_metadata(context): return False - if resolved.commit and not self._commit_metadata(resolved.tag): + if resolved.commit and not self._commit_metadata(resolved.tag, context): return False if resolved.compile_pdf and not self._compile(resolved): @@ -159,7 +160,7 @@ def release(self, options: ReleaseOptions) -> bool: created_tag = True print_success(f"Created release tag: {resolved.tag}") - if not self._refresh_metadata(): + if not self._refresh_metadata(context): self._print_rollback(resolved.tag) return False @@ -283,9 +284,21 @@ def _run_bibliography(self, policy: str) -> bool: ) return self.bibliography_service_cls(self.config).update(args, options) - def _refresh_metadata(self) -> bool: + def _refresh_metadata(self, context: ProjectContext) -> bool: """Refresh gitinfo2 metadata and print the current summary.""" + if context.is_typst and context.document is not None: + snapshot = refresh_typst_metadata(context.document.parent) + if snapshot is not None: + print_info(f"Version metadata: {snapshot.description}") + return True + print_error("Could not refresh Typst Git metadata.") + return False if refresh_gitinfo2_metadata(self.manager.repo_root): + if context.document is not None: + source = self.manager.repo_root / "gitHeadLocal.gin" + target = context.document.parent / "gitHeadLocal.gin" + if source.is_file() and source != target: + target.write_bytes(source.read_bytes()) summary = gitinfo2_metadata_summary(self.manager.repo_root) if summary: print_info(f"Version metadata: {summary}") @@ -293,15 +306,19 @@ def _refresh_metadata(self) -> bool: print_error("Could not refresh gitinfo2 metadata.") return False - def _commit_metadata(self, tag: str) -> bool: + def _commit_metadata(self, tag: str, context: ProjectContext) -> bool: """Commit gitHeadLocal.gin only when explicitly requested.""" - metadata_path = self.manager.repo_root / "gitHeadLocal.gin" + metadata_path = ( + context.document.parent / TYPST_METADATA_FILE + if context.is_typst and context.document is not None + else self.manager.repo_root / "gitHeadLocal.gin" + ) if not metadata_path.exists(): - print_warning("gitHeadLocal.gin does not exist; nothing to commit.") + print_warning(f"{metadata_path.name} does not exist; nothing to commit.") return True self.manager.commit_paths( - ["gitHeadLocal.gin"], - f"Update gitinfo2 metadata before release {tag}", + [metadata_path.relative_to(self.manager.repo_root).as_posix()], + f"Update paper version metadata before release {tag}", ) return True @@ -523,11 +540,15 @@ def pdf_contains_text(pdf_path: Path, text: str, runner: CommandRunner) -> bool: print_error(result.stderr.strip() or "Could not extract PDF text") return False if text not in result.stdout: - print_error(f"PDF text does not contain release tag {text}.") - print_info("Rebuild the PDF after tagging, or disable with --no-pdf-check.") + print_error(f"PDF text does not contain expected version {text}.") + print_info( + "Display the generated Git description in the document and rebuild. " + "For LaTeX use gitinfo2's \\gitDescribe; " + "for Typst display the snapshot's description field." + ) return False - print_success(f"PDF text contains release tag {text}") + print_success(f"PDF text contains expected version {text}") return True diff --git a/src/article_cli/templates/article/ieee.tex.j2 b/src/article_cli/templates/article/ieee.tex.j2 index 4787afe..ccaeb88 100644 --- a/src/article_cli/templates/article/ieee.tex.j2 +++ b/src/article_cli/templates/article/ieee.tex.j2 @@ -5,7 +5,7 @@ \usepackage{amsmath,amssymb} \usepackage{graphicx} \usepackage{hyperref} -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} \title{[[ title ]]} \author{[[ authors_latex ]]} @@ -48,7 +48,7 @@ Add acknowledgements here. \vfill \hrule \small -\noindent Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) \\ +\noindent Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) \\ Branch: \gitBranch \end{document} diff --git a/src/article_cli/templates/article/lncs.tex.j2 b/src/article_cli/templates/article/lncs.tex.j2 index eca01fd..1cf587a 100644 --- a/src/article_cli/templates/article/lncs.tex.j2 +++ b/src/article_cli/templates/article/lncs.tex.j2 @@ -4,7 +4,7 @@ \usepackage{graphicx} \usepackage{amsmath,amssymb} \usepackage{hyperref} -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} \title{[[ title ]]} \author{[[ authors_latex ]]} @@ -45,7 +45,7 @@ Add acknowledgements here. \vfill \hrule \small -\noindent Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) \\ +\noindent Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) \\ Branch: \gitBranch \end{document} diff --git a/src/article_cli/templates/article/lncs.typ.j2 b/src/article_cli/templates/article/lncs.typ.j2 index 4ca2ece..c9feb65 100644 --- a/src/article_cli/templates/article/lncs.typ.j2 +++ b/src/article_cli/templates/article/lncs.typ.j2 @@ -1,3 +1,5 @@ +// Generated by article-cli version; preserved in source archives. +#let git = json("gitHeadLocal.json") // Typst Article, LNCS-oriented skeleton // Title: [[ title ]] // Authors: [[ authors_display ]] @@ -18,6 +20,7 @@ paper: "a4", margin: (x: 3.0cm, y: 3.0cm), numbering: "1", + footer: align(center, text(size: 8pt)[Version: #git.description]), ) #set text(font: "Libertinus Serif", size: 10pt) diff --git a/src/article_cli/templates/article/main.tex.j2 b/src/article_cli/templates/article/main.tex.j2 index 80c9631..3f5f9d3 100644 --- a/src/article_cli/templates/article/main.tex.j2 +++ b/src/article_cli/templates/article/main.tex.j2 @@ -14,7 +14,7 @@ \addbibresource{references.bib} % Git version information -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} % Title and authors \title{[[ title ]]} @@ -52,7 +52,7 @@ Your conclusion goes here. \vfill \hrule \small -\noindent Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) \\ +\noindent Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) \\ Branch: \gitBranch \end{document} diff --git a/src/article_cli/templates/article/main.typ.j2 b/src/article_cli/templates/article/main.typ.j2 index 38aa61b..790f96b 100644 --- a/src/article_cli/templates/article/main.typ.j2 +++ b/src/article_cli/templates/article/main.typ.j2 @@ -1,3 +1,5 @@ +// Generated by article-cli version; preserved in source archives. +#let git = json("gitHeadLocal.json") // Typst Article // Title: [[ title ]] // Authors: [[ authors_display ]] @@ -15,6 +17,7 @@ paper: "a4", margin: (x: 2.5cm, y: 2.5cm), numbering: "1", + footer: align(center, text(size: 8pt)[Version: #git.description]), ) #set text(font: "Libertinus Serif", size: 11pt) diff --git a/src/article_cli/templates/poster/poster.tex.j2 b/src/article_cli/templates/poster/poster.tex.j2 index 5d6a54d..9345613 100644 --- a/src/article_cli/templates/poster/poster.tex.j2 +++ b/src/article_cli/templates/poster/poster.tex.j2 @@ -6,7 +6,7 @@ \usepackage{booktabs} % Git version information -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} % Title and authors \title{[[ title ]]} @@ -49,7 +49,7 @@ } \note[targetoffsetx=0cm, targetoffsety=-8cm, width=0.4\textwidth]{ - Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) + Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) } \end{document} diff --git a/src/article_cli/templates/poster/poster.typ.j2 b/src/article_cli/templates/poster/poster.typ.j2 index 775243c..b167167 100644 --- a/src/article_cli/templates/poster/poster.typ.j2 +++ b/src/article_cli/templates/poster/poster.typ.j2 @@ -1,3 +1,6 @@ +// Generated by article-cli version; preserved in source archives. +#let git = json("gitHeadLocal.json") +#set page(footer: align(right, text(size: 8pt)[Version: #git.description])) // Typst Poster // Title: [[ title ]] // Authors: [[ authors_display ]] diff --git a/src/article_cli/templates/presentation/beamer.tex.j2 b/src/article_cli/templates/presentation/beamer.tex.j2 index 4494488..27eae52 100644 --- a/src/article_cli/templates/presentation/beamer.tex.j2 +++ b/src/article_cli/templates/presentation/beamer.tex.j2 @@ -15,7 +15,7 @@ % \addbibresource{references.bib} % Git version information -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} % Title and authors \title{[[ title ]]} @@ -62,7 +62,7 @@ \vspace{1cm} \small - Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) + Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) \end{frame} \end{document} diff --git a/src/article_cli/templates/presentation/typst.typ.j2 b/src/article_cli/templates/presentation/typst.typ.j2 index 3187a56..80607e1 100644 --- a/src/article_cli/templates/presentation/typst.typ.j2 +++ b/src/article_cli/templates/presentation/typst.typ.j2 @@ -1,3 +1,6 @@ +// Generated by article-cli version; preserved in source archives. +#let git = json("gitHeadLocal.json") +#set page(footer: align(right, text(size: 8pt)[Version: #git.description])) [% if has_theme %] #import "[[ theme ]].typ": * diff --git a/src/article_cli/templates/project/README.md.j2 b/src/article_cli/templates/project/README.md.j2 index 35a1a42..d1dc8eb 100644 --- a/src/article_cli/templates/project/README.md.j2 +++ b/src/article_cli/templates/project/README.md.j2 @@ -118,7 +118,7 @@ article-cli bib update --include-local --merged-output references.all.bib # Build the configured document article-cli compile -# Refresh gitinfo2 version metadata +# Refresh paper version metadata (gitinfo2 for LaTeX, JSON for Typst) article-cli version # Refresh metadata, compile, and check the PDF version text diff --git a/src/article_cli/typst_compiler.py b/src/article_cli/typst_compiler.py index e76d7d5..2d48047 100644 --- a/src/article_cli/typst_compiler.py +++ b/src/article_cli/typst_compiler.py @@ -13,6 +13,7 @@ from typing import Dict, List, Optional from .config import Config +from .paper_version import refresh_typst_metadata from .reporting import print_error, print_info, print_success @@ -53,6 +54,8 @@ def compile( print_error(f"Typst file not found: {typ_file}") return False + if refresh_typst_metadata(typ_path.resolve().parent) is not None: + print_info("Updated Typst Git version metadata") print_info(f"Compiling {typ_file} with Typst...") # Merge font paths from config and arguments @@ -60,6 +63,12 @@ def compile( if font_paths: all_font_paths.extend(font_paths) + if output_dir: + try: + Path(output_dir).mkdir(parents=True, exist_ok=True) + except OSError as error: + print_error(f"Cannot create Typst output directory: {error}") + return False if watch: return self._compile_watch(typ_path, output_dir, all_font_paths) else: diff --git a/tests/golden/article_main.tex b/tests/golden/article_main.tex index a91c824..ac768b3 100644 --- a/tests/golden/article_main.tex +++ b/tests/golden/article_main.tex @@ -14,7 +14,7 @@ \addbibresource{references.bib} % Git version information -\usepackage{gitinfo2} +\usepackage[local]{gitinfo2} % Title and authors \title{Golden Article} @@ -52,7 +52,7 @@ \section{Conclusion} \vfill \hrule \small -\noindent Git version: \gitAbbrevHash{} (\gitAuthorIsoDate) \\ +\noindent Git version: \gitDescribe{} / \gitAbbrevHash{} (\gitAuthorIsoDate) \\ Branch: \gitBranch \end{document} diff --git a/tests/test_bundle.py b/tests/test_bundle.py new file mode 100644 index 0000000..6c3ae94 --- /dev/null +++ b/tests/test_bundle.py @@ -0,0 +1,272 @@ +"""Source selection, archive integrity, and isolated verification contracts.""" + +import hashlib +import json +import shutil +import subprocess +import zipfile + +import pytest + +from article_cli.cli import create_parser +from article_cli.commands.bundle import run +from article_cli.config import Config +from article_cli.services.bundle import BundleOptions, BundleService + + +@pytest.fixture +def project(tmp_path): + """Recorder data resembles latexmk with a separate output directory.""" + sources = { + "paper.tex": r"\documentclass{custom}\input{sections/body}\bibliography{refs}", + "custom.cls": "custom class", + "sections/body.tex": "body", + "figures/plot.pdf": "figure", + "refs.bib": "@article{key,title={Title}}", + "custom.bst": "bibliography style", + "build/paper.bbl": "formatted bibliography", + "build/paper.aux": "\\bibdata{refs}\n\\bibstyle{custom}\n", + "build/paper.pdf": "compiled main PDF", + "build/paper.log": "successful compilation", + ".git": "gitdir: /private/worktree", + "local-only/agreement.pdf": "private agreement", + ".env": "private credentials", + "notes.md": "extra notes", + "supplement.csv.gz": "compressed supplement", + } + for name, data in sources.items(): + path = tmp_path / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(data) + inputs = [ + "paper.tex", + "custom.cls", + "sections/body.tex", + "figures/plot.pdf", + "build/paper.aux", + "build/paper.bbl", + "build/paper.log", + "build/paper.pdf", + ] + (tmp_path / "build/paper.fls").write_text( + f"PWD {tmp_path}\nINPUT /usr/share/texmf/article.cls\n" + + "".join(f"INPUT {name}\n" for name in inputs) + ) + config_path = tmp_path / "pyproject.toml" + config_path.write_text( + """ +[tool.article-cli.documents] +main = "paper.tex" +[tool.article-cli.workflow] +output_dir = "build" +""" + ) + return tmp_path, config_path + + +def service_for(project, policy="", **kwargs): + root, config_path = project + if policy: + with config_path.open("a") as stream: + stream.write("\n[tool.article-cli.bundle]\n" + policy) + return BundleService(Config(config_path, quiet=True), cwd=root, **kwargs) + + +def test_archive_contains_dependencies_and_portable_manifest(project): + service = service_for(project) + plan = service.plan(BundleOptions(no_build=True)) + expected = { + "paper.tex", + "custom.cls", + "sections/body.tex", + "figures/plot.pdf", + "paper.bbl", + "refs.bib", + "custom.bst", + } + assert set(plan.files) == expected + archive = service.write(plan) + original = archive.read_bytes() + with zipfile.ZipFile(archive) as zipped: + assert set(zipped.namelist()) == expected + assert zipped.read("paper.bbl") == b"formatted bibliography" + manifest = json.loads(archive.with_suffix(".manifest.json").read_text()) + assert manifest["document"] == "paper.tex" + assert manifest["verified"] is False + assert str(project[0]) not in json.dumps(manifest) + assert manifest["archive_sha256"] == hashlib.sha256(original).hexdigest() + with pytest.raises(ValueError, match="already exists"): + service.write(plan) + service.write(plan, force=True) + assert archive.read_bytes() == original + + +def test_config_and_cli_patterns_extend_with_exclude_precedence(project): + service = service_for(project, 'include = ["**/*"]\nexclude = ["notes.md"]\n') + plan = service.plan( + BundleOptions( + no_build=True, exclude=("pyproject.toml",), include=("supplement.csv.gz",) + ) + ) + assert "supplement.csv.gz" in plan.files + assert ( + not { + ".git", + ".env", + "local-only/agreement.pdf", + "notes.md", + "pyproject.toml", + "build/paper.pdf", + } + & plan.files.keys() + ) + + +def test_pdf_policy_can_be_overridden(project): + service = service_for(project, "include_pdf = true\n") + assert "paper.pdf" in service.plan(BundleOptions(no_build=True)).files + assert ( + "paper.pdf" + not in service.plan(BundleOptions(no_build=True, include_pdf=False)).files + ) + + +@pytest.mark.parametrize( + "exclude", ["sections/**", "**/*.bib", "paper.bbl", "custom.cls"] +) +def test_required_dependency_exclusion_fails(project, exclude): + with pytest.raises(ValueError, match="Required dependency excluded"): + service_for(project).plan(BundleOptions(no_build=True, exclude=(exclude,))) + + +@pytest.mark.parametrize( + "pattern", ["../secret", "/private/secret", "foo/../../secret", r"C:\secret"] +) +def test_unsafe_patterns_rejected(project, pattern): + with pytest.raises(ValueError, match="must stay inside"): + service_for(project).plan(BundleOptions(no_build=True, include=(pattern,))) + + +def test_missing_include_is_an_error(project): + with pytest.raises(ValueError, match="matched no files"): + service_for(project).plan(BundleOptions(no_build=True, include=("missing/**",))) + + +@pytest.mark.parametrize("name", ["build/paper.bbl", "refs.bib", "sections/body.tex"]) +def test_missing_required_files_are_errors(project, name): + (project[0] / name).unlink() + with pytest.raises(ValueError, match="Missing"): + service_for(project).plan(BundleOptions(no_build=True)) + + +def test_symlink_escape_rejected(project, tmp_path_factory): + outside = tmp_path_factory.mktemp("outside") / "secret" + outside.write_text("private") + (project[0] / "linked").symlink_to(outside) + with pytest.raises(ValueError, match="outside the document directory"): + service_for(project).plan(BundleOptions(no_build=True, include=("linked",))) + + +def test_dry_run_writes_nothing(project, monkeypatch, capsys): + monkeypatch.chdir(project[0]) + args = create_parser().parse_args(["bundle", "--dry-run"]) + assert run(args, Config(project[1], quiet=True)) == 0 + assert "paper.bbl" in capsys.readouterr().out + assert not (project[0] / "dist").exists() + + +def test_missing_recorder_gives_actionable_error(project): + (project[0] / "build/paper.fls").unlink() + with pytest.raises(ValueError, match="Build first"): + service_for(project).plan(BundleOptions(dry_run=True)) + + +def test_root_build_directory_keeps_source_dependencies(project): + root, config_path = project + (root / "paper.fls").write_text( + (root / "build/paper.fls").read_text().replace("build/paper", "paper") + ) + shutil.copy(root / "build/paper.aux", root / "paper.aux") + shutil.copy(root / "build/paper.bbl", root / "paper.bbl") + plan = service_for(project).plan(BundleOptions(no_build=True, build_dir=".")) + assert {"custom.cls", "sections/body.tex", "refs.bib"} <= plan.files.keys() + + +def test_verification_failure_does_not_publish(project, monkeypatch): + service = service_for(project) + plan = service.plan(BundleOptions(no_build=True, verify=True)) + + def fail(build): + assert build.root != project[0] + assert (build.root / "paper.bbl").is_file() + assert not (build.root / "local-only").exists() + raise ValueError("compilation failed") + + monkeypatch.setattr(service.backends["latexmk"], "compile", fail) + with pytest.raises(ValueError, match="compilation failed"): + service.write(plan) + assert not plan.output.exists() + assert not plan.output.with_suffix(".manifest.json").exists() + + +def test_compiler_failure_and_undefined_citations(project): + class Runner: + result = subprocess.CompletedProcess([], 1, "broken source", "") + + def run(self, command, **kwargs): + assert "-no-shell-escape" in command + assert "-recorder" in command + return self.result + + runner = Runner() + service = service_for(project, runner=runner) + with pytest.raises(ValueError, match="broken source"): + service.plan(BundleOptions()) + runner.result = subprocess.CompletedProcess([], 0, "", "") + (project[0] / "build/paper.log").write_text("There were undefined references.") + with pytest.raises(ValueError, match="unresolved citations"): + service.plan(BundleOptions()) + + +def test_clean_verification_with_real_latexmk(tmp_path): + if not shutil.which("latexmk") or not shutil.which("pdflatex"): + pytest.skip("LaTeX tools are not installed") + (tmp_path / "paper.tex").write_text( + r"\documentclass{article}\begin{document}Hello\input{body}\end{document}" + ) + (tmp_path / "body.tex").write_text("World") + config = tmp_path / "bundle.toml" + config.write_text('[documents]\nmain="paper.tex"\n') + service = BundleService(Config(config, quiet=True), cwd=tmp_path) + plan = service.plan(BundleOptions(verify=True)) + output = service.write(plan) + with zipfile.ZipFile(output) as zipped: + assert set(zipped.namelist()) == {"paper.tex", "body.tex"} + assert ( + json.loads(output.with_suffix(".manifest.json").read_text())["verified"] is True + ) + + +def test_real_biblatex_bundle_carries_biber_inputs(tmp_path): + if not all(shutil.which(tool) for tool in ("latexmk", "pdflatex", "biber")): + pytest.skip("LaTeX and Biber are required") + (tmp_path / "paper.tex").write_text( + r"""\documentclass{article} +\usepackage{biblatex} +\addbibresource{refs.bib} +\begin{document}See \cite{test}.\printbibliography\end{document} +""" + ) + (tmp_path / "refs.bib").write_text( + "@article{test,title={Testing},author={Doe, Jane},year={2026},journal={Tests}}" + ) + config = tmp_path / "config.toml" + config.write_text('[documents]\nmain="paper.tex"\n') + service = BundleService(Config(config, quiet=True), cwd=tmp_path) + plan = service.plan(BundleOptions(verify=True)) + output = service.write(plan) + with zipfile.ZipFile(output) as archive: + assert {"paper.tex", "refs.bib", "paper.bbl"} <= set(archive.namelist()) + assert not any( + name.endswith((".bcf", ".run.xml")) for name in archive.namelist() + ) diff --git a/tests/test_bundle_typst.py b/tests/test_bundle_typst.py new file mode 100644 index 0000000..30eec83 --- /dev/null +++ b/tests/test_bundle_typst.py @@ -0,0 +1,332 @@ +"""Typst parity and the engine strategy boundary for submission bundles.""" + +import json +import shutil +import subprocess +import zipfile + +import pytest + +from article_cli.cli import create_parser +from article_cli.commands.bundle import run +from article_cli.config import Config +from article_cli.services.bundle import BundleOptions, BundleService +from article_cli.services.bundle_backends import BundleDependency, TYPST_PACKAGES + + +@pytest.fixture +def typst_project(tmp_path): + files = { + "paper.typ": '#include "sections/body.typ"', + "sections/body.typ": "Nested content", + "figures/plot.svg": "", + "data/results.json": '{"value":42}', + "refs.bib": "@article{test,title={Test}}", + "style.csl": "custom bibliography style", + "notes.md": "extra notes", + ".env": "secret", + "local-only/agreement.pdf": "private", + "fonts/project.ttf": "font", + "build/paper.pdf": "%PDF", + } + for name, content in files.items(): + path = tmp_path / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + (tmp_path / "build/paper.typst-deps.json").write_text( + json.dumps( + { + "inputs": [ + "paper.typ", + "sections/body.typ", + "figures/plot.svg", + "data/results.json", + "refs.bib", + "style.csl", + ], + "outputs": ["build/paper.pdf"], + } + ) + ) + config = tmp_path / "pyproject.toml" + config.write_text( + """ +[tool.article-cli.documents] +main = "paper.typ" +[tool.article-cli.typst] +build_dir = "build" +font_paths = ["fonts"] +[tool.article-cli.bundle] +include = ["notes.md"] +verify = false +""" + ) + return tmp_path, config + + +def service_for(project, **kwargs): + root, config = project + return BundleService(Config(config, quiet=True), cwd=root, **kwargs) + + +def test_typst_dependency_archive_and_manifest(typst_project): + service = service_for(typst_project) + plan = service.plan(BundleOptions(no_build=True)) + assert plan.engine == "typst" + assert set(plan.files) == { + "paper.typ", + "sections/body.typ", + "figures/plot.svg", + "data/results.json", + "refs.bib", + "style.csl", + "fonts/project.ttf", + "notes.md", + } + output = service.write(plan) + with zipfile.ZipFile(output) as archive: + assert set(archive.namelist()) == set(plan.files) + metadata = json.loads(output.with_suffix(".manifest.json").read_text()) + assert metadata["engine"] == "typst" + assert metadata["font_paths"] == ["fonts"] + assert str(typst_project[0]) not in json.dumps(metadata) + before = output.read_bytes() + service.write(plan, force=True) + assert output.read_bytes() == before + + +def test_typst_cli_dry_run_and_overrides(typst_project, monkeypatch, capsys): + root, config = typst_project + monkeypatch.chdir(root) + args = create_parser().parse_args( + ["bundle", "paper.typ", "--dry-run", "--exclude", "notes.md", "--include-pdf"] + ) + assert args.document == "paper.typ" + assert run(args, Config(config, quiet=True)) == 0 + output = capsys.readouterr().out + assert "paper.pdf" in output + assert "notes.md" not in output + assert not (root / "dist").exists() + + +@pytest.mark.parametrize("excluded", ["sections/**", "**/*.bib", "data/**", "fonts/**"]) +def test_typst_required_exclusions_fail(typst_project, excluded): + with pytest.raises(ValueError, match="Required dependency excluded"): + service_for(typst_project).plan( + BundleOptions(no_build=True, exclude=(excluded,)) + ) + + +def test_typst_broad_includes_keep_private_files_and_metadata_out(typst_project): + plan = service_for(typst_project).plan( + BundleOptions(no_build=True, include=("**/*",)) + ) + assert ( + not { + ".env", + "local-only/agreement.pdf", + "build/paper.typst-deps.json", + "build/paper.pdf", + } + & plan.files.keys() + ) + + +@pytest.mark.parametrize("metadata", ["not json", "{}", "[]", '{"inputs":[1]}']) +def test_typst_bad_dependency_data(typst_project, metadata): + (typst_project[0] / "build/paper.typst-deps.json").write_text(metadata) + with pytest.raises(ValueError, match="metadata"): + service_for(typst_project).plan(BundleOptions(no_build=True)) + + +def test_typst_missing_metadata_actionable(typst_project): + (typst_project[0] / "build/paper.typst-deps.json").unlink() + with pytest.raises(ValueError, match="Build first"): + service_for(typst_project).plan(BundleOptions(dry_run=True)) + + +def test_typst_missing_source_fails(typst_project): + (typst_project[0] / "sections/body.typ").unlink() + with pytest.raises(ValueError, match="Missing bundle dependency"): + service_for(typst_project).plan(BundleOptions(no_build=True)) + + +def test_typst_symlink_escape_fails(typst_project, tmp_path_factory): + outside = tmp_path_factory.mktemp("external") / "secret" + outside.write_text("secret") + path = typst_project[0] / "sections/body.typ" + path.unlink() + path.symlink_to(outside) + with pytest.raises(ValueError, match="outside the document directory"): + service_for(typst_project).plan(BundleOptions(no_build=True)) + + +def test_typst_external_file_not_silently_omitted(typst_project, tmp_path_factory): + outside = tmp_path_factory.mktemp("external") / "secret" + outside.write_text("secret") + (typst_project[0] / "build/paper.typst-deps.json").write_text( + json.dumps({"inputs": [str(outside)]}) + ) + with pytest.raises(ValueError, match="not a package"): + service_for(typst_project).plan(BundleOptions(no_build=True)) + + +def test_typst_compiler_errors_do_not_publish(typst_project): + class Runner: + def run(self, command, **kwargs): + assert command[:2] == ["typst", "compile"] + assert "--deps-format" in command + assert command[command.index("--font-path") + 1].endswith("/fonts") + return subprocess.CompletedProcess(command, 1, "", "unknown label") + + with pytest.raises(ValueError, match="unknown label"): + service_for(typst_project, runner=Runner()).plan(BundleOptions()) + assert not (typst_project[0] / "dist").exists() + + +def test_custom_backend_can_be_injected_without_inheritance(tmp_path): + """The service depends on the protocol, not either concrete compiler.""" + document = tmp_path / "main.typ" + document.write_text("Hello") + config = tmp_path / "config.toml" + config.write_text('[documents]\nmain="main.typ"\n') + + class Backend: + suffix = ".typ" + builds = [] + + def compile(self, build): + self.builds.append(build) + + def dependencies(self, build): + return [BundleDependency(build.document, "main.typ", build.root)] + + backend = Backend() + service = BundleService( + Config(config, quiet=True), cwd=tmp_path, backends={"typst": backend} + ) + plan = service.plan(BundleOptions(verify=True)) + service.write(plan) + assert len(backend.builds) == 2 + assert backend.builds[0].root == tmp_path + assert backend.builds[1].root != tmp_path + assert backend.builds[1].font_paths == () + + +def require_typst(): + if not shutil.which("typst"): + pytest.skip("Typst is not installed") + help_text = subprocess.run( + ["typst", "compile", "--help"], capture_output=True, text=True, check=True + ).stdout + if "--deps-format" not in help_text: + pytest.skip("Typst 0.14+ is required for native dependency reporting") + + +def test_real_typst_dynamic_dependencies_and_clean_verification(tmp_path): + require_typst() + (tmp_path / "sections").mkdir() + (tmp_path / "sections/body.typ").write_text("Nested body") + (tmp_path / "observations.log").write_text("Recorded observations") + (tmp_path / "results.json").write_text('{"message":"Dynamic data"}') + (tmp_path / "plot.svg").write_text( + '' + ) + (tmp_path / "refs.bib").write_text( + "@article{test, title={Testing}, author={Doe, Jane}, year={2026}, journal={Tests}}" + ) + (tmp_path / "main.typ").write_text( + """ +#include "sections/body.typ" +#let filename = "results" + ".json" +#json(filename).message +#read("observations.log") +#image("plot.svg") +See @test. +#bibliography("refs.bib") +""" + ) + config = tmp_path / "config.toml" + config.write_text('[documents]\nmain="main.typ"\n') + service = BundleService(Config(config, quiet=True), cwd=tmp_path) + plan = service.plan(BundleOptions(verify=True, include_pdf=True)) + output = service.write(plan) + with zipfile.ZipFile(output) as archive: + assert set(archive.namelist()) == { + "main.typ", + "sections/body.typ", + "results.json", + "observations.log", + "plot.svg", + "refs.bib", + "main.pdf", + } + assert json.loads(output.with_suffix(".manifest.json").read_text())["verified"] + + +@pytest.mark.parametrize("namespace", ["local", "preview"]) +def test_real_typst_external_package_is_vendored_and_rebuilt(tmp_path, namespace): + require_typst() + project = tmp_path / "project" + project.mkdir() + package = tmp_path / f"packages/{namespace}/example/1.0.0" + package.mkdir(parents=True) + (package / "typst.toml").write_text( + '[package]\nname="example"\nversion="1.0.0"\nentrypoint="lib.typ"\n' + ) + (package / "lib.typ").write_text("#let greeting = [Vendored package]") + (package / "LICENSE").write_text("Test package license") + (project / "main.typ").write_text( + f'#import "@{namespace}/example:1.0.0": greeting\n#greeting' + ) + build = project / "build" + build.mkdir() + # Produce real dependency metadata using an external local package installation. + subprocess.run( + [ + "typst", + "compile", + "--root", + str(project), + "--package-path", + str(tmp_path / "packages"), + "--deps", + str(build / "main.typst-deps.json"), + "--deps-format", + "json", + "main.typ", + str(build / "main.pdf"), + ], + cwd=project, + capture_output=True, + text=True, + check=True, + ) + config = project / "config.toml" + config.write_text('[documents]\nmain="main.typ"\n[typst]\nbuild_dir="build"\n') + service = BundleService(Config(config, quiet=True), cwd=project) + plan = service.plan(BundleOptions(no_build=True, verify=True)) + prefix = f"{TYPST_PACKAGES}/{namespace}/example/1.0.0" + assert { + f"{prefix}/lib.typ", + f"{prefix}/typst.toml", + f"{prefix}/LICENSE", + } <= plan.files.keys() + output = service.write(plan) + assert json.loads(output.with_suffix(".manifest.json").read_text())["verified"] + + +def test_verification_relocates_configured_fonts(typst_project, monkeypatch): + service = service_for(typst_project) + plan = service.plan(BundleOptions(no_build=True, verify=True)) + builds = [] + + def compile(build): + builds.append(build) + assert build.root != typst_project[0] + assert build.font_paths == ("fonts",) + assert (build.root / "fonts/project.ttf").read_text() == "font" + + monkeypatch.setattr(service.backends["typst"], "compile", compile) + service.write(plan) + assert len(builds) == 1 diff --git a/tests/test_cli.py b/tests/test_cli.py index 928e39f..928da58 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -64,10 +64,10 @@ def test_compile_uses_project_config_when_cli_args_absent(tmp_path, monkeypatch) assert result == 0 compile_mock.assert_called_once_with( - tex_file="paper.tex", + tex_file=str(tmp_path / "paper.tex"), engine="xelatex", shell_escape=True, - output_dir="build", + output_dir=str(tmp_path / "build"), watch=False, ) @@ -108,10 +108,10 @@ def test_compile_cli_args_override_project_config(tmp_path, monkeypatch): assert result == 0 compile_mock.assert_called_once_with( - tex_file="paper.tex", + tex_file=str(tmp_path / "paper.tex"), engine="pdflatex", shell_escape=False, - output_dir="out", + output_dir=str(tmp_path / "out"), watch=False, ) @@ -280,11 +280,12 @@ def test_setup_dry_run_is_forwarded_to_git_manager(): manager_cls.return_value.setup_hooks.assert_called_once_with(dry_run=True) -def test_version_dry_run_is_forwarded_to_git_manager(): +def test_version_dry_run_is_forwarded_to_git_manager(tmp_path): """version --dry-run should not refresh files.""" args = SimpleNamespace(dry_run=True) with patch("article_cli.commands.version.GitManager") as manager_cls: + manager_cls.return_value.repo_root = tmp_path manager_cls.return_value.refresh_version_metadata.return_value = True result = version_command.run(args, Config(quiet=True)) diff --git a/tests/test_paper_version.py b/tests/test_paper_version.py new file mode 100644 index 0000000..0296a6a --- /dev/null +++ b/tests/test_paper_version.py @@ -0,0 +1,231 @@ +"""Git snapshot lifecycle and versioned documents with real compilers.""" + +import json +import shutil +import subprocess +import zipfile + +import pytest + +from article_cli.config import Config +from article_cli.git_hooks import render_gitinfo2_metadata +from article_cli.git_manager import GitManager +from article_cli.paper_version import ( + read_git_snapshot, + refresh_typst_metadata, + write_typst_snapshot, +) +from article_cli.services.bundle import BundleOptions, BundleService +from article_cli.services.gitinfo import GitInfoService, VersionOptions +from article_cli.services.release import ReleaseOptions, ReleaseService +from article_cli.typst_compiler import TypstCompiler + + +def git(root, *args): + return subprocess.run( + ["git", "-c", "core.hooksPath=/dev/null", *args], + cwd=root, + text=True, + capture_output=True, + check=True, + ).stdout.strip() + + +@pytest.fixture +def repo(tmp_path): + git(tmp_path, "init", "-q") + git(tmp_path, "config", "user.name", "Version Test") + git(tmp_path, "config", "user.email", "test@example.invalid") + (tmp_path / "paper.typ").write_text( + '#let git = json("gitHeadLocal.json")\nVersion: #git.description' + ) + (tmp_path / "pyproject.toml").write_text( + """ +[tool.article-cli.documents] +main = "paper.typ" +[tool.article-cli.typst] +build_dir = "build" +""" + ) + (tmp_path / "gitHeadLocal.json").write_text("{}") + (tmp_path / "gitHeadLocal.gin").write_text("old metadata") + git(tmp_path, "add", ".") + git(tmp_path, "commit", "-qm", "Baseline") + git(tmp_path, "tag", "v1") + return tmp_path + + +def test_snapshot_tag_hash_branch_and_dirty_state(repo): + clean = read_git_snapshot(repo) + assert clean is not None and not clean.dirty + assert clean.commit == git(repo, "rev-parse", "HEAD") + assert clean.release == "v1" + assert clean.description.startswith("v1-0-g") + assert clean.branch == git(repo, "branch", "--show-current") + assert clean.commit_date + (repo / "paper.typ").write_text("Changed source") + dirty = read_git_snapshot(repo) + assert dirty.dirty and dirty.description.endswith("-*") + assert "-*" in render_gitinfo2_metadata(repo) + + +def test_generated_metadata_does_not_contaminate_dirty_flag(repo): + (repo / "gitHeadLocal.gin").write_text("refreshed metadata") + snapshot = refresh_typst_metadata(repo) + assert snapshot is not None and not snapshot.dirty + first = (repo / "gitHeadLocal.json").read_bytes() + assert refresh_typst_metadata(repo) == snapshot + assert (repo / "gitHeadLocal.json").read_bytes() == first + assert not GitManager(repo).dirty_files(ignore_gitinfo=True) + assert "-*" not in render_gitinfo2_metadata(repo) + + +def test_detached_head_and_nested_document(repo): + git(repo, "checkout", "--detach", "-q") + directory = repo / "nested" + directory.mkdir() + snapshot = refresh_typst_metadata(directory) + assert snapshot.branch == "detached" + assert (directory / "gitHeadLocal.json").is_file() + assert snapshot.commit == git(repo, "rev-parse", "HEAD") + + +def test_archive_snapshot_preserved_without_git(tmp_path): + snapshot = tmp_path / "gitHeadLocal.json" + snapshot.write_text('{"description":"v1"}') + assert refresh_typst_metadata(tmp_path) is None + assert snapshot.read_text() == '{"description":"v1"}' + + +def test_version_dry_run_does_not_write(repo): + config = Config(repo / "pyproject.toml", quiet=True) + before = (repo / "gitHeadLocal.json").read_bytes() + assert GitInfoService(config, repo_root=repo).refresh(VersionOptions(dry_run=True)) + assert (repo / "gitHeadLocal.json").read_bytes() == before + + +def test_typst_snapshot_strings_are_safely_encoded(repo): + from dataclasses import replace + + snapshot = replace(read_git_snapshot(repo), branch='a"quote\\backslash_é') + path = write_typst_snapshot(snapshot, repo) + assert json.loads(path.read_text())["branch"] == snapshot.branch + + +def test_typst_compile_refreshes_metadata(repo): + class Compiler(TypstCompiler): + def _compile_once(self, typ_path, output_dir, font_paths): + assert ( + json.loads((typ_path.parent / "gitHeadLocal.json").read_text())[ + "release" + ] + == "v1" + ) + return True + + assert Compiler(Config(repo / "pyproject.toml", quiet=True)).compile( + str(repo / "paper.typ") + ) + + +def test_version_compile_check_and_frozen_typst_bundle(repo): + if not shutil.which("typst") or not shutil.which("pdftotext"): + pytest.skip("Typst and pdftotext are required") + config = Config(repo / "pyproject.toml", quiet=True) + service = GitInfoService(config, repo_root=repo) + assert service.refresh( + VersionOptions(compile_pdf=True, check_pdf=True, checksum=True) + ) + snapshot = json.loads((repo / "gitHeadLocal.json").read_text()) + assert snapshot["release"] == "v1" and not snapshot["dirty"] + bundle = BundleService(config, cwd=repo) + output = bundle.write(bundle.plan(BundleOptions(verify=True))) + with zipfile.ZipFile(output) as archive: + assert set(archive.namelist()) == {"paper.typ", "gitHeadLocal.json"} + assert json.loads(archive.read("gitHeadLocal.json")) == snapshot + assert json.loads(output.with_suffix(".manifest.json").read_text())["verified"] + + +def test_typst_release_refreshes_version_after_tagging(repo): + if not shutil.which("typst") or not shutil.which("pdftotext"): + pytest.skip("Typst and pdftotext are required") + config = Config(repo / "pyproject.toml", quiet=True) + assert ReleaseService(config, repo_root=repo).release(ReleaseOptions(tag="v2")) + snapshot = json.loads((repo / "gitHeadLocal.json").read_text()) + assert snapshot["release"] == "v2" + assert snapshot["description"].startswith("v2-0-g") + + +def test_latex_local_gitinfo2_survives_archive_verification(repo): + if not shutil.which("latexmk") or not shutil.which("pdftotext"): + pytest.skip("LaTeX and pdftotext are required") + (repo / "paper.tex").write_text( + r"""\documentclass{article} +\usepackage[local]{gitinfo2} +\begin{document} +Version: \gitDescribe{} / \gitAbbrevHash{} +\end{document} +""" + ) + config = Config(repo / "pyproject.toml", quiet=True) + assert GitInfoService(config, repo_root=repo).refresh( + VersionOptions( + document="paper.tex", + engine="pdflatex", + compile_pdf=True, + check_pdf=True, + tag="v1", + ) + ) + # Select the LaTeX engine rather than the configured Typst main document. + latex_config = repo / "latex.toml" + latex_config.write_text( + '[documents]\nmain="paper.tex"\n[latex]\nbuild_dir="build"\n' + ) + service = BundleService(Config(latex_config, quiet=True), cwd=repo) + output = service.write(service.plan(BundleOptions(verify=True))) + with zipfile.ZipFile(output) as archive: + assert "gitHeadLocal.gin" in archive.namelist() + assert all(not name.startswith(".git/") for name in archive.namelist()) + + +def test_typst_presentation_stamp_keeps_page_count(tmp_path): + if not shutil.which("typst") or not shutil.which("pdftotext"): + pytest.skip("Typst and pdftotext are required") + from article_cli.paper_version import GitSnapshot + from article_cli.repository_setup import RepositorySetup + + text = RepositorySetup(tmp_path)._get_typst_presentation_template( + "Title", ["Author"], "" + ) + (tmp_path / "slides.typ").write_text(text) + write_typst_snapshot(GitSnapshot(description="v1"), tmp_path) + subprocess.run( + ["typst", "compile", "slides.typ"], + cwd=tmp_path, + capture_output=True, + text=True, + check=True, + ) + result = subprocess.run( + ["pdftotext", str(tmp_path / "slides.pdf"), "-"], + capture_output=True, + text=True, + check=True, + ).stdout + assert result.count("\f") == 5 + assert result.count("Version: v1") == 5 + + +@pytest.mark.parametrize("action", ["compile_pdf", "check_pdf", "checksum"]) +def test_missing_document_fails_before_metadata_refresh(repo, action, capsys): + (repo / "paper.typ").unlink() + before = (repo / "gitHeadLocal.json").read_bytes() + gin_before = (repo / "gitHeadLocal.gin").read_bytes() + service = GitInfoService( + Config(repo / "pyproject.toml", quiet=True), repo_root=repo + ) + assert not service.refresh(VersionOptions(**{action: True})) + assert (repo / "gitHeadLocal.json").read_bytes() == before + assert (repo / "gitHeadLocal.gin").read_bytes() == gin_before + assert str(repo) in capsys.readouterr().err diff --git a/tests/test_release_service.py b/tests/test_release_service.py index 97e81f2..d63b870 100644 --- a/tests/test_release_service.py +++ b/tests/test_release_service.py @@ -191,3 +191,27 @@ def run(self, command, **kwargs): assert "PDF pages: 4" in output assert f"Checksum: {checksum_path}" in output assert "SHA256:" in output + + +def test_missing_pdf_version_explains_how_to_display_metadata( + tmp_path, monkeypatch, capsys +): + from article_cli.services.release import pdf_contains_text + + class Runner: + def run(self, command, **kwargs): + return subprocess.CompletedProcess( + command, 0, "Paper without a version stamp", "" + ) + + pdf = tmp_path / "paper.pdf" + pdf.write_bytes(b"fixture") + monkeypatch.setattr( + "article_cli.services.release.shutil.which", lambda name: "/usr/bin/pdftotext" + ) + assert not pdf_contains_text(pdf, "v1-0-gabcdef0-*", Runner()) + captured = capsys.readouterr() + assert "expected version v1-0-gabcdef0-*" in captured.err + assert "gitDescribe" in captured.out + assert "Typst" in captured.out + assert "--no-pdf-check" not in captured.out diff --git a/tests/test_services.py b/tests/test_services.py index 3fd00bb..fc82cb5 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -154,10 +154,10 @@ def test_compiler_service_dispatches_latex_with_shared_policy(tmp_path): assert service.compile(CompileOptions(clean_first=True, clean_after=True)) is True assert FakeLatexCompiler.instances[0].calls == [ { - "tex_file": "paper.tex", + "tex_file": str(tmp_path / "paper.tex"), "engine": "xelatex", "shell_escape": True, - "output_dir": "build", + "output_dir": str(tmp_path / "build"), "watch": False, } ] @@ -182,7 +182,7 @@ def test_compiler_service_dispatches_typst(tmp_path): assert service.compile(CompileOptions(font_paths=["fonts"], watch=True)) is True assert FakeTypstCompiler.instances[0].calls == [ { - "typ_file": "main.typ", + "typ_file": str(tmp_path / "main.typ"), "output_dir": None, "font_paths": ["fonts"], "watch": True, diff --git a/uv.lock b/uv.lock index 436ecef..03093ef 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "article-cli" -version = "2.0.1" +version = "2.1.0" source = { editable = "." } dependencies = [ { name = "jinja2" }, From dcd4b31df23e3cff3d4c01ee4291709ce4c4e923 Mon Sep 17 00:00:00 2001 From: Christophe Prud'homme Date: Fri, 30 Oct 2026 12:41:55 +0100 Subject: [PATCH 2/3] fix(bundle): honor PDF policy and require Python 3.11 Exclude stale main PDFs and duplicate bibliography artifacts. Use standard-library TOML parsing and Twine 7. Add CI coverage with real LaTeX, Biber and Typst compilers. Update templates, documentation and dependency lockfile. Refs #20 --- .github/workflows/ci.yml | 38 +- AGENTS.md | 6 +- CHANGELOG.md | 7 + DEVELOPMENT.md | 10 + README.md | 6 +- build-and-test.sh | 9 - docs/antora.yml | 2 +- docs/modules/ROOT/pages/install.adoc | 2 +- pyproject.toml | 13 +- src/article_cli/config.py | 16 +- src/article_cli/services/bundle.py | 10 +- src/article_cli/services/bundle_backends.py | 4 +- .../templates/project/README.md.j2 | 2 +- .../templates/project/pyproject.toml.j2 | 2 +- tests/test_bundle.py | 39 + tests/test_repository_setup.py | 5 +- tests/test_version_consistency.py | 5 +- tests/test_workflow_ci.py | 5 +- uv.lock | 741 ++---------------- 19 files changed, 172 insertions(+), 750 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc24c78..20e777a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -28,7 +28,7 @@ jobs: - name: Install dependencies with uv run: | - uv sync --all-extras --dev --python ${{ matrix.python-version }} + uv sync --locked --all-extras --dev --python ${{ matrix.python-version }} - name: Lint with flake8 run: | @@ -62,10 +62,40 @@ jobs: echo "- ✅ CLI installation: Passed" >> $GITHUB_STEP_SUMMARY echo "- ✅ Doctor JSON smoke test: Passed" >> $GITHUB_STEP_SUMMARY + compiler-integration: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v8.1.0 + with: + version: "latest" + enable-cache: true + + - name: Set up Python + run: uv python install 3.11 + + - name: Install dependencies with uv + run: uv sync --locked --all-extras --dev --python 3.11 + + - name: Set up Typst + uses: typst-community/setup-typst@v4 + with: + typst-version: "0.15.1" + + - name: Install LaTeX and PDF tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends latexmk texlive-latex-extra texlive-fonts-recommended biber poppler-utils + + - name: Verify archives and versioned PDFs with real compilers + run: uv run --locked pytest tests/test_bundle.py tests/test_bundle_typst.py tests/test_paper_version.py + build: needs: test runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 @@ -80,7 +110,7 @@ jobs: run: uv python install 3.11 - name: Install dependencies with uv - run: uv sync --all-extras --dev --python 3.11 + run: uv sync --locked --all-extras --dev --python 3.11 - name: Build package run: uv build diff --git a/AGENTS.md b/AGENTS.md index 3da589a..b8ce684 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,7 +103,7 @@ article.cli/ ### Updating Dependencies 1. Update `pyproject.toml` dependencies section -2. Test with different Python versions (3.8-3.12) +2. Test with different Python versions (3.11-3.13) 3. Update CI if needed 4. Document any breaking changes @@ -120,7 +120,7 @@ article.cli/ 1. **Build and Test** (`ci.yml`): - Triggered on: push to main/develop, pull requests - - Tests: Python 3.8, 3.9, 3.10, 3.11, 3.12 + - Tests: Python 3.11, 3.12, 3.13 - Checks: Black, flake8, mypy, CLI functionality - Artifacts: Built package for distribution @@ -191,7 +191,7 @@ uv pip install black flake8 mypy types-requests pytest - **Default Zotero Group ID**: `4678293` (for article.template) - **Default Output File**: `references.bib` - **Default Branch**: `main` -- **Python Support**: 3.8 - 3.12 +- **Python Support**: 3.11+ ## 🔗 Important Links diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf4658..1558a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Portable Typst Git snapshots with version/commit/branch/date/dirty state, integrated with `version`, `compile`, templates, and paper release workflows. LaTeX templates use local `gitinfo2` snapshots for archive portability. - Adjacent JSON manifests record source hashes and the archive checksum; existing archives require `--force` to replace. +### Changed +- Require Python 3.11+ for article-cli and generated projects; remove the TOML backport and use the standard-library parser. +- Use Twine 7 to validate modern package metadata and run package validation on pull requests. +- Exercise source bundles and versioned PDFs in CI with real LaTeX, Biber and Typst compilers. + +### Fixed +- Broad bundle include patterns respect the main-PDF policy and do not add stale PDFs or duplicate generated bibliography files. ## [1.4.0] - 2026-01-18 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4cd79f9..1c6e018 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,6 +1,16 @@ # Article CLI Development Setup This guide helps you set up the article-cli package for development and distribution. +Python 3.11 or newer is required. + +CI runs archive and paper-version tests in a dedicated compiler integration job with +LaTeX, Biber, Typst 0.15.1 and Poppler installed. To run the same checks locally: + +```bash +uv run pytest tests/test_bundle.py tests/test_bundle_typst.py tests/test_paper_version.py +``` + +These tests skip compiler-specific cases when the corresponding tools are unavailable locally. ## Development Setup diff --git a/README.md b/README.md index 3dc8c8b..3ebe756 100644 --- a/README.md +++ b/README.md @@ -533,7 +533,7 @@ Use `[tool.article-cli.release] tag_policy = "semver"` when a repository must re ## Requirements -- Python 3.9+ +- Python 3.11+ - Git repository with gitinfo2 package (for LaTeX integration) - Zotero account with API access (for bibliography features) - Typst CLI (for Typst compilation) - install from https://typst.app/ @@ -553,6 +553,10 @@ MIT License - see LICENSE file for details. ## Changelog +### v2.1.0 +- Require Python 3.11+ and use its built-in TOML parser +- Add LaTeX and Typst submission bundles and portable paper version metadata + ### v2.0.1 - Keep Python package, generated-template minimum version, Antora component version, docs package version, and lockfiles synchronized - Add a release-version bump script and CI test to prevent stale documentation version labels diff --git a/build-and-test.sh b/build-and-test.sh index 2147253..8e384ed 100755 --- a/build-and-test.sh +++ b/build-and-test.sh @@ -35,15 +35,6 @@ run_command() { echo -e "${YELLOW}📦 Installing dependencies...${NC}" run_command "pip install -e ." -# Check if optional dependencies are available -echo -e "${YELLOW}🧪 Checking optional dependencies...${NC}" -if python -c "import tomli" 2>/dev/null; then - echo -e "${GREEN}✅ tomli available for Python < 3.11${NC}" -else - echo -e "${YELLOW}⚠️ Installing tomli for TOML support${NC}" - run_command "pip install tomli" -fi - # Build the package echo -e "${YELLOW}🏗️ Building package...${NC}" if ! command -v build &> /dev/null; then diff --git a/docs/antora.yml b/docs/antora.yml index 9687c6b..eebfd7d 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -9,7 +9,7 @@ asciidoc: attributes: project-name: article-cli cli-command: article-cli - python-supported: '3.9, 3.10, 3.11, 3.12, 3.13' + python-supported: '3.11, 3.12, 3.13' article-cli-version: '2.1.0' template-version: '1' zotero-group-example: '4709047' diff --git a/docs/modules/ROOT/pages/install.adoc b/docs/modules/ROOT/pages/install.adoc index abdaf2a..276e0e0 100644 --- a/docs/modules/ROOT/pages/install.adoc +++ b/docs/modules/ROOT/pages/install.adoc @@ -41,7 +41,7 @@ uv run mypy src/ uv build ---- -The GitHub Actions workflow runs the test suite across Python 3.9 through 3.13. +The GitHub Actions workflow runs the test suite across Python 3.11 through 3.13. == Documentation Site diff --git a/pyproject.toml b/pyproject.toml index 2e8cf22..5a35600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,15 +11,13 @@ authors = [ description = "CLI tool for managing LaTeX and Typst documents with git integration and Zotero bibliography" readme = "README.md" license = {file = "LICENSE"} -requires-python = ">=3.9" +requires-python = ">=3.11" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -31,7 +29,6 @@ dependencies = [ "requests>=2.28.0", "PyYAML>=6.0", "Jinja2>=3.1", - "tomli>=2.0.0; python_version < '3.11'", ] [project.optional-dependencies] @@ -42,7 +39,7 @@ dev = [ "flake8>=6.0", "mypy>=1.0,<2.0", "build>=1.2.0", - "twine>=5.0.0", + "twine>=7.0.0", "types-requests", ] @@ -54,7 +51,7 @@ dev = [ "flake8>=6.0", "mypy>=1.0,<2.0", "build>=1.2.0", - "twine>=5.0.0", + "twine>=7.0.0", "types-requests", ] @@ -80,7 +77,7 @@ include = [ [tool.black] line-length = 88 -target-version = ['py39'] +target-version = ['py311'] include = '\.pyi?$' extend-exclude = ''' /( @@ -97,7 +94,7 @@ extend-exclude = ''' ''' [tool.mypy] -python_version = "3.9" +python_version = "3.11" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = false # Relax for now to avoid breaking changes diff --git a/src/article_cli/config.py b/src/article_cli/config.py index 3a4ccc1..7841fa6 100644 --- a/src/article_cli/config.py +++ b/src/article_cli/config.py @@ -9,13 +9,7 @@ from typing import Dict, Any, Optional, Union import argparse -try: - import tomllib # Python 3.11+ -except ImportError: - try: - import tomli as tomllib # fallback for older Python versions - except ImportError: - tomllib = None +import tomllib class Config: @@ -67,7 +61,7 @@ def _load_config(self) -> None: config_path = self._find_config_file() self.loaded_config_file = config_path.resolve() if config_path else None - if config_path and tomllib: + if config_path: try: with open(config_path, "rb") as f: full_config = tomllib.load(f) @@ -104,12 +98,6 @@ def _load_config(self) -> None: if not self.quiet: print(f"Warning: Could not load config file {config_path}: {e}") self._config_data = {} - elif config_path and not tomllib: - if not self.quiet: - print( - "Warning: TOML support not available. Install dependencies with: uv sync" - ) - self._config_data = {} else: self._config_data = {} diff --git a/src/article_cli/services/bundle.py b/src/article_cli/services/bundle.py index 51cc35a..69b1a78 100644 --- a/src/article_cli/services/bundle.py +++ b/src/article_cli/services/bundle.py @@ -230,6 +230,10 @@ def add(dependency: BundleDependency, needed: bool = False) -> None: add(BundleDependency(document, document.name, root), needed=True) for dependency in backend.dependencies(build): add(dependency, needed=True) + # Broad includes must not reintroduce stale main PDFs or compiler-output + # aliases for dependencies already placed at their portable archive paths. + automatic_sources = {path.resolve() for path in files.values()} + main_pdf_sources = {main_pdf.resolve(), document.with_suffix(".pdf").resolve()} for pattern in include: matches = list(root.glob(pattern)) if not matches: @@ -237,7 +241,11 @@ def add(dependency: BundleDependency, needed: bool = False) -> None: for match in matches: paths = match.rglob("*") if match.is_dir() else [match] for path in paths: - if path.is_file(): + if ( + path.is_file() + and path.resolve() not in automatic_sources + and path.resolve() not in main_pdf_sources + ): add( BundleDependency( path, path.relative_to(root).as_posix(), root diff --git a/src/article_cli/services/bundle_backends.py b/src/article_cli/services/bundle_backends.py index a2a90fc..420ec35 100644 --- a/src/article_cli/services/bundle_backends.py +++ b/src/article_cli/services/bundle_backends.py @@ -8,13 +8,13 @@ import json import os import re +import tomllib from xml.etree import ElementTree from dataclasses import dataclass from pathlib import Path from typing import Iterable, Mapping, Optional, Protocol, Tuple from ..command_runner import CommandRunner -from ..config import tomllib from ..project_context import LATEX_ENGINES TYPST_PACKAGES = ".article-cli/typst-packages" @@ -314,8 +314,6 @@ def _package_root(source: Path) -> Optional[Path]: if not manifest.is_file(): continue try: - if tomllib is None: - raise ValueError("TOML support is required for Typst packages.") data = tomllib.loads(manifest.read_text()) package = data.get("package", {}) if ( diff --git a/src/article_cli/templates/project/README.md.j2 b/src/article_cli/templates/project/README.md.j2 index d1dc8eb..40b8e3e 100644 --- a/src/article_cli/templates/project/README.md.j2 +++ b/src/article_cli/templates/project/README.md.j2 @@ -14,7 +14,7 @@ Document style: `[[ style ]]` ## Prerequisites -- Python 3.9+ for bibliography management +- Python 3.11+ for bibliography management - LaTeX distribution, with TeX Live recommended - Git with the gitinfo2 package diff --git a/src/article_cli/templates/project/pyproject.toml.j2 b/src/article_cli/templates/project/pyproject.toml.j2 index 73e80f0..863bcbc 100644 --- a/src/article_cli/templates/project/pyproject.toml.j2 +++ b/src/article_cli/templates/project/pyproject.toml.j2 @@ -11,7 +11,7 @@ authors = [ [% endfor %] ] readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.11" dependencies = [ "article-cli>=[[ article_cli_min_version ]]", # Add other dependencies your project might need: diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 6c3ae94..67d3933 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -270,3 +270,42 @@ def test_real_biblatex_bundle_carries_biber_inputs(tmp_path): assert not any( name.endswith((".bcf", ".run.xml")) for name in archive.namelist() ) + + +@pytest.mark.parametrize("suffix", [".tex", ".typ"]) +@pytest.mark.parametrize("include_pdf", [False, True]) +def test_broad_includes_respect_main_pdf_policy(tmp_path, suffix, include_pdf): + """Old PDFs and build-path aliases must not override main-PDF policy.""" + (tmp_path / "paper").with_suffix(suffix).write_text("Paper") + (tmp_path / "paper.pdf").write_text("stale PDF from an earlier build") + build = tmp_path / "build" + build.mkdir() + (build / "paper.pdf").write_text("current main PDF") + (tmp_path / "figure.pdf").write_text("required figure PDF") + if suffix == ".tex": + (build / "paper.fls").write_text( + f"PWD {tmp_path}\nINPUT paper.tex\nINPUT figure.pdf\n" + ) + else: + (build / "paper.typst-deps.json").write_text( + json.dumps({"inputs": ["paper.typ", "figure.pdf"]}) + ) + config = tmp_path / "config.toml" + config.write_text( + f'[documents]\nmain="paper{suffix}"\n' + '[workflow]\noutput_dir="build"\n' + '[bundle]\ninclude=["**/*"]\n' + ) + service = BundleService(Config(config, quiet=True), cwd=tmp_path) + plan = service.plan(BundleOptions(no_build=True, include_pdf=include_pdf)) + output = service.write(plan) + with zipfile.ZipFile(output) as archive: + pdfs = {name for name in archive.namelist() if name.endswith(".pdf")} + assert pdfs == ({"figure.pdf", "paper.pdf"} if include_pdf else {"figure.pdf"}) + if include_pdf: + assert archive.read("paper.pdf") == b"current main PDF" + + +def test_broad_includes_do_not_duplicate_generated_bibliography(project): + plan = service_for(project).plan(BundleOptions(no_build=True, include=("**/*",))) + assert {name for name in plan.files if name.endswith(".bbl")} == {"paper.bbl"} diff --git a/tests/test_repository_setup.py b/tests/test_repository_setup.py index e9d5133..404b663 100644 --- a/tests/test_repository_setup.py +++ b/tests/test_repository_setup.py @@ -17,10 +17,7 @@ from article_cli.repository_setup import ARTICLE_CLI_MIN_VERSION, RepositorySetup from article_cli.template_renderer import TEMPLATE_VERSION -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - exercised on Python < 3.11 - import tomli as tomllib # type: ignore[no-redef] +import tomllib def init_git_repository(path: Path) -> None: diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py index 86b2a52..a79c42f 100644 --- a/tests/test_version_consistency.py +++ b/tests/test_version_consistency.py @@ -10,10 +10,7 @@ import article_cli from article_cli.repository_setup import ARTICLE_CLI_MIN_VERSION -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - Python 3.9/3.10 - import tomli as tomllib +import tomllib ROOT = Path(__file__).resolve().parents[1] diff --git a/tests/test_workflow_ci.py b/tests/test_workflow_ci.py index d20b0f7..6d6cb81 100644 --- a/tests/test_workflow_ci.py +++ b/tests/test_workflow_ci.py @@ -8,10 +8,7 @@ from article_cli.repository_setup import RepositorySetup -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - exercised on Python < 3.11 - import tomli as tomllib # type: ignore[no-redef] +import tomllib def _init_project(tmp_path: Path, **kwargs: object) -> tuple[str, dict]: diff --git a/uv.lock b/uv.lock index 03093ef..c301e61 100644 --- a/uv.lock +++ b/uv.lock @@ -1,11 +1,6 @@ version = 1 -revision = 3 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] +revision = 5 +requires-python = ">=3.11" [[package]] name = "article-cli" @@ -14,39 +9,31 @@ source = { editable = "." } dependencies = [ { name = "jinja2" }, { name = "pyyaml" }, - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "requests" }, ] [package.optional-dependencies] dev = [ { name = "black" }, - { name = "build", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "build", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "build" }, { name = "flake8" }, { name = "mypy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "pytest-cov" }, { name = "twine" }, - { name = "types-requests", version = "2.32.4.20260107", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "types-requests", version = "2.33.0.20260508", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "types-requests" }, ] [package.dev-dependencies] dev = [ { name = "black" }, - { name = "build", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "build", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "build" }, { name = "flake8" }, { name = "mypy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "pytest-cov" }, { name = "twine" }, - { name = "types-requests", version = "2.32.4.20260107", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "types-requests", version = "2.33.0.20260508", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "types-requests" }, ] [package.metadata] @@ -60,8 +47,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "requests", specifier = ">=2.28.0" }, - { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.0" }, - { name = "twine", marker = "extra == 'dev'", specifier = ">=5.0.0" }, + { name = "twine", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "types-requests", marker = "extra == 'dev'" }, ] provides-extras = ["dev"] @@ -74,7 +60,7 @@ dev = [ { name = "mypy", specifier = ">=1.0,<2.0" }, { name = "pytest", specifier = ">=7.0" }, { name = "pytest-cov" }, - { name = "twine", specifier = ">=5.0.0" }, + { name = "twine", specifier = ">=7.0.0" }, { name = "types-requests" }, ] @@ -92,23 +78,15 @@ name = "black" version = "25.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click" }, { name = "mypy-extensions" }, { name = "packaging" }, { name = "pathspec" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs" }, { name = "pytokens" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" }, - { url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" }, - { url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" }, { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, @@ -125,46 +103,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" }, { url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" }, { url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" }, - { url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" }, - { url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" }, { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, ] -[[package]] -name = "build" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and os_name == 'nt'" }, - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pyproject-hooks", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/ec/bf5ae0a7e5ab57abe8aabdd0759c971883895d1a20c49ae99f8146840c3c/build-1.4.4.tar.gz", hash = "sha256:f832ae053061f3fb524af812dc94b8b84bac6880cd587630e3b5d91a6a9c1703", size = 89220, upload-time = "2026-04-22T20:53:44.807Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/88/6764e7a109dd84294850741501145da90d13cdeac9d4e614929464a37420/build-1.4.4-py3-none-any.whl", hash = "sha256:8c3f48a6090b39edec1a273d2d57949aaf13723b01e02f9d518396887519f64d", size = 25921, upload-time = "2026-04-22T20:53:43.251Z" }, -] - [[package]] name = "build" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and os_name == 'nt'" }, - { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.10.2'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pyproject-hooks", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } wheels = [ @@ -185,17 +134,10 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, - { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, @@ -220,12 +162,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, - { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, - { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, ] [[package]] @@ -234,22 +170,6 @@ version = "3.4.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, - { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, - { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, - { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, @@ -330,50 +250,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/ef725f8eb19b5a261b30f78efa9252ef9d017985cb499102f6f49834cd12/charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217", size = 299121, upload-time = "2026-04-02T09:28:14.372Z" }, - { url = "https://files.pythonhosted.org/packages/a3/22/2f12878fbc680fbbb52386cd39a379801f62eaca74fc8b323381325f0f04/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5", size = 200612, upload-time = "2026-04-02T09:28:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/bc/b6/10c84e789126ca97d4a7228863a30481e786980a8b8cfcbf4f30658ca63c/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9", size = 221041, upload-time = "2026-04-02T09:28:17.554Z" }, - { url = "https://files.pythonhosted.org/packages/21/7b/c414866a138400b2e81973d006da7f694cfeaf895ef07d2cba9a8743841a/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a", size = 216323, upload-time = "2026-04-02T09:28:18.863Z" }, - { url = "https://files.pythonhosted.org/packages/2e/92/bdcf94997e06b223d826df3abed45a5ad6e17f609b7df9d25cd23b5bde30/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc", size = 208419, upload-time = "2026-04-02T09:28:20.332Z" }, - { url = "https://files.pythonhosted.org/packages/1a/64/3f9142293c88b1b10e199649ed1330f070c2a68e305335a5819fa7f25fa7/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00", size = 195016, upload-time = "2026-04-02T09:28:21.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d1/d8a6b7dd5c5636b76ce0d080bc57d8e56c7bbd6bc2ac941529a35e41d84a/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776", size = 206115, upload-time = "2026-04-02T09:28:23.259Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8c/60ebe912379627d023eb96995b40bc50308729f210f43d66109ca0a7bbd2/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319", size = 204022, upload-time = "2026-04-02T09:28:24.779Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2a/41816ceda78a551cbfdfbeab6f3891152b0e3f758ce6580c2c18c829f774/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24", size = 195914, upload-time = "2026-04-02T09:28:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9b/7c7f4b7f11525fcbdfba752455314ac60646bae91cdd671d531c1f7a97c6/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42", size = 222159, upload-time = "2026-04-02T09:28:27.504Z" }, - { url = "https://files.pythonhosted.org/packages/9f/57/301682e7469bdbfa2ce219a804f0668b2266ab8520570d85d3b3ef483ea3/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4", size = 206154, upload-time = "2026-04-02T09:28:28.848Z" }, - { url = "https://files.pythonhosted.org/packages/20/ec/90339ff5cdc598b265748c1f231c7d7fbd9123a92cee10f757e0b1448de4/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67", size = 217423, upload-time = "2026-04-02T09:28:30.248Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e7/a7a6147f8e3375676309cf584b25c72a3bab784ea4085b0011fa07b23aeb/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274", size = 210604, upload-time = "2026-04-02T09:28:31.736Z" }, - { url = "https://files.pythonhosted.org/packages/1a/62/d9340c7a79c393e57807d7fb6c57e82060687891f81b74d3201958b919c1/charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366", size = 144631, upload-time = "2026-04-02T09:28:33.158Z" }, - { url = "https://files.pythonhosted.org/packages/21/e7/92901117e2ddc8facfe8235a3ecd4eb482185b2ad5d5b6606b37c1afea06/charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444", size = 154710, upload-time = "2026-04-02T09:28:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/cc/4f/e1fb138201ad9a32499dd9a98aa4a5a5441fbf7f56b52b619a54b7ee8777/charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c", size = 143716, upload-time = "2026-04-02T09:28:35.908Z" }, { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - [[package]] name = "click" version = "8.3.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ @@ -389,149 +274,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.10.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, - { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, - { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, - { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, - { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, - { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, - { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, - { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, - { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, - { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, - { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, - { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, - { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, - { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, - { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, - { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, - { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, - { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, - { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, - { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, - { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, - { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, - { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, - { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, - { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, - { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, - { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, - { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, - { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, - { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, - { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, - { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, - { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, - { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, - { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, - { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, - { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version < '3.10'" }, -] - [[package]] name = "coverage" version = "7.13.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, - { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, - { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, - { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, - { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, - { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, - { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, @@ -627,66 +375,15 @@ wheels = [ [package.optional-dependencies] toml = [ - { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, -] - -[[package]] -name = "cryptography" -version = "47.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "cffi", marker = "python_full_version <= '3.9' and platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version <= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, - { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, - { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, - { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, - { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, - { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, - { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, - { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, - { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, - { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, - { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, - { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, - { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, - { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, - { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, - { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, - { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, - { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/81/75/d691e284750df5d9569f2b1ce4a00a71e1d79566da83b2b3e5549c84917f/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1a405c08857258c11016777e11c02bacbe7ef596faf259305d282272a3a05cbe", size = 4587867, upload-time = "2026-04-24T19:54:40.619Z" }, - { url = "https://files.pythonhosted.org/packages/07/d6/1b90f1a4e453009730b4545286f0b39bb348d805c11181fc31544e4f9a65/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:20fdbe3e38fb67c385d233c89371fa27f9909f6ebca1cecc20c13518dae65475", size = 4627192, upload-time = "2026-04-24T19:54:42.849Z" }, - { url = "https://files.pythonhosted.org/packages/dc/53/cb358a80e9e359529f496870dd08c102aa8a4b5b9f9064f00f0d6ed5b527/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50", size = 4587486, upload-time = "2026-04-24T19:54:44.908Z" }, - { url = "https://files.pythonhosted.org/packages/8b/57/aaa3d53876467a226f9a7a82fd14dd48058ad2de1948493442dfa16e2ffd/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9fe6b7c64926c765f9dff301f9c1b867febcda5768868ca084e18589113732ab", size = 4626327, upload-time = "2026-04-24T19:54:47.813Z" }, + { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "cryptography" version = "48.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version > '3.9' and python_full_version < '3.10'", -] dependencies = [ - { name = "cffi", marker = "python_full_version > '3.9' and platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version > '3.9' and python_full_version < '3.11'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } wheels = [ @@ -732,18 +429,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - [[package]] name = "flake8" version = "7.3.0" @@ -763,8 +448,7 @@ name = "id" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } wheels = [ @@ -780,57 +464,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "zipp", marker = "python_full_version >= '3.10'" }, + { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -841,39 +490,19 @@ name = "jaraco-classes" version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "more-itertools", version = "11.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "more-itertools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, ] -[[package]] -name = "jaraco-context" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "backports-tarfile", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/7b/c3081ff1af947915503121c649f26a778e1a2101fd525f74aef997d75b7e/jaraco_context-6.1.1.tar.gz", hash = "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581", size = 15832, upload-time = "2026-03-07T15:46:04.63Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl", hash = "sha256:0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808", size = 7005, upload-time = "2026-03-07T15:46:03.515Z" }, -] - [[package]] name = "jaraco-context" version = "6.1.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ @@ -885,8 +514,7 @@ name = "jaraco-functools" version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "more-itertools", version = "11.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "more-itertools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ @@ -919,16 +547,13 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, { name = "jaraco-classes" }, - { name = "jaraco-context", version = "6.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "jaraco-context", version = "6.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jaraco-context" }, { name = "jaraco-functools" }, { name = "jeepney", marker = "sys_platform == 'linux'" }, { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, - { name = "secretstorage", version = "3.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and sys_platform == 'linux'" }, - { name = "secretstorage", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ @@ -941,18 +566,6 @@ version = "0.10.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/39/cb/c1945e506893b5b8577fb45a60c80e3ffe4a82092a04a6f29b0b951d9a24/librt-0.10.0.tar.gz", hash = "sha256:1aba1e8aa4e3307a7be68a74149545fde7451964dc0235a8bec5704a17bdda42", size = 191799, upload-time = "2026-05-05T16:31:23.535Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/18/827e5c1262a88c2602e86f99aee0f288ffea3280dbd2ff448858ef9dc6e9/librt-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dc99f9642100b86e5f6bb14cdc9970009e31a9ef7d64df6704b7018451524a3", size = 76461, upload-time = "2026-05-05T16:29:00.422Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/54254e30287f5a5abec6fef22d976987476e966be5fdff51fe8c2d5d73d1/librt-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8298cedfcfaff3790000bd057aaaa3df1b0ab54cf7b48eeab16184cbb1bc66b9", size = 79740, upload-time = "2026-05-05T16:29:01.926Z" }, - { url = "https://files.pythonhosted.org/packages/4c/20/e93264b52113669d98d3b63ff94d4ce0c4dd49ae0503f1788440a884e5f0/librt-0.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7dbe312dbf76468255b79a7ba311236fde620f2f7055fc09d421e31340314e", size = 243472, upload-time = "2026-05-05T16:29:03.373Z" }, - { url = "https://files.pythonhosted.org/packages/35/ad/34a5141178e8b18a4cfa45d1a0d523c84397e2abd5d06fea2d846da687e8/librt-0.10.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:56ed90c48c19249012dadfd79a1bc13bd5168ea60a70722d330a3a600c0b1852", size = 232073, upload-time = "2026-05-05T16:29:04.815Z" }, - { url = "https://files.pythonhosted.org/packages/97/1f/67240e910cd9f9ab1498c1470738345fc29dce5dc9719db1e0e09d1e861f/librt-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d74ca0f4b2b09c117f913d4df01f6b934dff8a271096b35167d5264a31649f0", size = 256956, upload-time = "2026-05-05T16:29:06.516Z" }, - { url = "https://files.pythonhosted.org/packages/22/50/3a2b3482c27d607f6e8216d913c6bc592b9a2141d96990309452340a78e3/librt-0.10.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8eb2daa9375f93c0e55ff5e44a4bbe98f39e5fe52e1abf9c97acb67743b61bf8", size = 250593, upload-time = "2026-05-05T16:29:08.324Z" }, - { url = "https://files.pythonhosted.org/packages/e7/1c/07dba133d79f93322fa17514062f1a2a50d6bdfb7baec4acf78193d7fad1/librt-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b09b90e634e6dff57978cd358070046071e2b120501f10787aeb35425f504f6", size = 263582, upload-time = "2026-05-05T16:29:09.866Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ac/033f2c6d6ab0b48f15f02e5bf065521b11a51922806017f8b6274df30d69/librt-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf22fd379d60c739b800d4295ed34045f8b04aa8df9c12bd2f8f43f7fe672b7", size = 259307, upload-time = "2026-05-05T16:29:11.675Z" }, - { url = "https://files.pythonhosted.org/packages/6e/10/679046cd75d5a52c0104c890d8f69574ef4e619c683e59c15584d03a2457/librt-0.10.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:74c798793fcf29a84d442278ebe0bb1fff79fe58ac4106eeff7019cbba861423", size = 257342, upload-time = "2026-05-05T16:29:13.14Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d5/dbaac9c0884f78a53dda22b9ec92bb788e1400e762ed7623fa96928c8da5/librt-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f1573401e8dbe6c26511fe027620b0fb30ae9a7ab814e02e510626b8b5f9c", size = 280141, upload-time = "2026-05-05T16:29:14.922Z" }, - { url = "https://files.pythonhosted.org/packages/cc/81/71f18cf8eb340d9fda011498870910f6a8697aeb50833005d3d8107653fd/librt-0.10.0-cp310-cp310-win32.whl", hash = "sha256:e1428275f5fe3d4db6822e58d8b005a5b28ffca55e8433ebc051247fbe46429f", size = 62257, upload-time = "2026-05-05T16:29:16.226Z" }, - { url = "https://files.pythonhosted.org/packages/df/52/6bcebc2f870c4836bcb372be885fae7f17a1d25037d3a8250ef79fbe0124/librt-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:0708e9408f585b0f065081680583a577652099680ccf820c7538904322b679c3", size = 70321, upload-time = "2026-05-05T16:29:17.41Z" }, { url = "https://files.pythonhosted.org/packages/e2/a3/1472717d2325adacc8d335ba2e4078015c09d75b599f3cf48e967b3d306e/librt-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01b4500ca3a625450c032a9142a8e843923ce263fa8a92ad1b38927cabe2fe72", size = 76045, upload-time = "2026-05-05T16:29:18.731Z" }, { url = "https://files.pythonhosted.org/packages/a6/31/bfe32355d4b369aef3d7aa442df663bb5558c2ffa2de286cb2956346bc24/librt-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b7e42d1b3e300d20bfc87e72ffd62f0a92a2cb3c35f7bf90df90c9d2a49f74c", size = 79466, upload-time = "2026-05-05T16:29:20.052Z" }, { url = "https://files.pythonhosted.org/packages/e9/f1/83f8a2c715ba2cac9b7387a5a5cea25f717f7184320cfe48b36bed9c58e9/librt-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef7b8c61ce3a1b597cd3e15348ff1574325165c2e7ce09a718154cde2a7950", size = 242283, upload-time = "2026-05-05T16:29:21.596Z" }, @@ -1018,45 +631,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/3a/9c635ac3e8a00383ff689161d3eac8a30b3b2ddc711b40471e6b8983ea29/librt-0.10.0-cp314-cp314t-win32.whl", hash = "sha256:657860fd877fba6a241ea088ef99f63ca819945d3c715265da670bad56c37ebe", size = 60147, upload-time = "2026-05-05T16:31:00.293Z" }, { url = "https://files.pythonhosted.org/packages/dc/e8/6f65f3e565d4ac212cddddd552eacc8035ffdf941ca0ad6fe945a211d41f/librt-0.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56ded2d66010203a0cb5af063b609e3f079531a0e5e576d618dece859fd2e1af", size = 68649, upload-time = "2026-05-05T16:31:01.778Z" }, { url = "https://files.pythonhosted.org/packages/51/78/a0705a67cacd81e5fa01a5035b3adbdfbb43a7b8d4bd27e2b282ae61baf2/librt-0.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1ee63f30abf18ed4830fdbaf87b2b6f4bba1e198d46085c314edde4045e56715", size = 58247, upload-time = "2026-05-05T16:31:03.191Z" }, - { url = "https://files.pythonhosted.org/packages/5e/86/ba668426245b9531ae3f922f98e7f721149fb71aa81d5f0c5aea7ca49645/librt-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83628c28545a5f4d860b48fae7f62367c006ab7405898573f34af8b7dcb178a2", size = 77056, upload-time = "2026-05-05T16:31:04.798Z" }, - { url = "https://files.pythonhosted.org/packages/ab/28/818421ff2432527fdd39fcce8a5909a916d03d3f32b4eb5f3285e3faa998/librt-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bcf57b4de07e2d4bd093636ee59dc1b64298f304148dd9c4f001f7c7897650d", size = 80372, upload-time = "2026-05-05T16:31:06.404Z" }, - { url = "https://files.pythonhosted.org/packages/19/b9/a9d6bb3e7524d285c729d6f6fa840841cadeddcf7de11073608821fd9fdb/librt-0.10.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2236c16bdb7c527eb671e4b599eec2c4229fddf80573de2bde529924f46db971", size = 243806, upload-time = "2026-05-05T16:31:07.805Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1f/9ecf1003461f9004f16073975981dd1496c496823a56f3a476aff86d8825/librt-0.10.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:c1efa2f494811b245427095225a4d0251aee33ba4cf6ba2b7a6a9a619bc1a2ff", size = 232029, upload-time = "2026-05-05T16:31:09.338Z" }, - { url = "https://files.pythonhosted.org/packages/87/48/ed64ab6e460b853e4263c3dbcce0e016bad1ffd569f54774323604cb822e/librt-0.10.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d14626d350af79eed4b4f8886530052e3f78a62e9e53d2699f726f99c3d1d122", size = 257203, upload-time = "2026-05-05T16:31:10.9Z" }, - { url = "https://files.pythonhosted.org/packages/41/10/f632735eadc006120416a873e4a230dbfca86e7e9ed56b5d546203b0b89b/librt-0.10.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b609f3461beae5608ca5219131ae5cdfea2e369818030abfc6ba7086830cde42", size = 250735, upload-time = "2026-05-05T16:31:12.494Z" }, - { url = "https://files.pythonhosted.org/packages/92/cf/d9f64349396e777c1861dbe35bffed2e9b7fff88064ea5ba7e73dd6f14e3/librt-0.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e2338b67c8e72755ccd1ab77b027e3701b375a1e12b4576fdefdf9c46448274", size = 264026, upload-time = "2026-05-05T16:31:14.25Z" }, - { url = "https://files.pythonhosted.org/packages/4a/2d/56f578d74464f3912690c3532666a9b338c853c62a36fb7acdc4b0479dad/librt-0.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:17cadff57139ff49beea0b17e50b28dfc3f9687126399696de4d2d8ae86ba7ff", size = 259755, upload-time = "2026-05-05T16:31:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/b0/10/4c544e7622034a2692aa9c492ea9a705bae5fc35435edd001336a2c09460/librt-0.10.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:5496102c8ed065c128d0f0fd10dcb3f9f3fd9b346954462d62af623f1b1ec7cd", size = 257624, upload-time = "2026-05-05T16:31:17.73Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a9/7972d0146152adf1330b027746c6e4be9030e7a5a38b78addcfdb628bdca/librt-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:537e1bfa459c1c92a263768a8a0c6fd0558049fa6c1b866d791eea711ae64114", size = 280487, upload-time = "2026-05-05T16:31:19.591Z" }, - { url = "https://files.pythonhosted.org/packages/1b/4d/926856839d875659dfc23adb6ff295fdfca53a5b5f915319f23cb5611b73/librt-0.10.0-cp39-cp39-win32.whl", hash = "sha256:85aca5a7ddc5f2d4cba24eba35667d83893ff2980dbd5884be16f538a24351e4", size = 62623, upload-time = "2026-05-05T16:31:20.924Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f3/c017fe4337e263bac6a38d2768d687c06e82886d6c131c99179063006323/librt-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:e45e46ff5fdfc690e77bb8557d5ba56974c4006b744ddbd70cce99fec6bfbeb8", size = 70725, upload-time = "2026-05-05T16:31:22.182Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "mdurl", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.10'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1069,17 +651,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, @@ -1146,17 +717,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, - { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, - { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, - { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] @@ -1177,26 +737,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "more-itertools" -version = "10.8.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, -] - [[package]] name = "more-itertools" version = "11.0.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, @@ -1210,17 +754,10 @@ dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, @@ -1245,12 +782,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, - { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, - { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] @@ -1315,26 +846,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "platformdirs" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, -] - [[package]] name = "platformdirs" version = "4.9.6" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, @@ -1358,26 +873,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, ] -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, -] - [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -1410,43 +909,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - [[package]] name = "pytest" version = "9.0.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ @@ -1458,11 +930,9 @@ name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.13.5", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ @@ -1475,11 +945,6 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, @@ -1505,11 +970,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/51/2a/f125667ce48105bf1f4e50e03cfa7b24b8c4f47684d7f1cf4dcb6f6b1c15/pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", size = 161464, upload-time = "2026-01-30T01:03:39.11Z" }, - { url = "https://files.pythonhosted.org/packages/40/df/065a30790a7ca6bb48ad9018dd44668ed9135610ebf56a2a4cb8e513fd5c/pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", size = 246159, upload-time = "2026-01-30T01:03:40.131Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1c/fd09976a7e04960dabc07ab0e0072c7813d566ec67d5490a4c600683c158/pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", size = 259120, upload-time = "2026-01-30T01:03:41.233Z" }, - { url = "https://files.pythonhosted.org/packages/52/49/59fdc6fc5a390ae9f308eadeb97dfc70fc2d804ffc49dd39fc97604622ec/pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", size = 262196, upload-time = "2026-01-30T01:03:42.696Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e7/d6734dccf0080e3dc00a55b0827ab5af30c886f8bc127bbc04bc3445daec/pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", size = 103510, upload-time = "2026-01-30T01:03:43.915Z" }, { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] @@ -1528,15 +988,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, @@ -1584,15 +1035,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, - { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, - { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, - { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, - { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, - { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -1609,37 +1051,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, ] -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "certifi", marker = "python_full_version < '3.10'" }, - { name = "charset-normalizer", marker = "python_full_version < '3.10'" }, - { name = "idna", marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, -] - [[package]] name = "requests" version = "2.33.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.10'" }, - { name = "charset-normalizer", marker = "python_full_version >= '3.10'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ @@ -1651,8 +1071,7 @@ name = "requests-toolbelt" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } wheels = [ @@ -1673,8 +1092,7 @@ name = "rich" version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown-it-py" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } @@ -1682,34 +1100,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] -[[package]] -name = "secretstorage" -version = "3.3.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, - { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9' and python_full_version < '3.10'" }, - { name = "jeepney", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221, upload-time = "2022-08-13T16:22:44.457Z" }, -] - [[package]] name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "jeepney", marker = "python_full_version >= '3.10'" }, + { name = "cryptography" }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -1772,52 +1169,30 @@ wheels = [ [[package]] name = "twine" -version = "6.2.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "id" }, - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, { name = "packaging" }, { name = "readme-renderer" }, - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests" }, { name = "requests-toolbelt" }, { name = "rfc3986" }, { name = "rich" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" }, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl", hash = "sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size = 43204, upload-time = "2026-07-27T15:58:59.26Z" }, ] [[package]] name = "types-requests" version = "2.33.0.20260508" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/6b/eb226bdd61a982c9a03e02c657fb4ab001733506e6423906ac142331f2e3/types_requests-2.33.0.20260508.tar.gz", hash = "sha256:81b2ae5f0d20967714a6aa5ef9284c05570d7cb06b7de8f2a77b918b63ddd411", size = 23991, upload-time = "2026-05-08T04:50:56.818Z" } wheels = [ @@ -1833,26 +1208,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - [[package]] name = "urllib3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, From ecc97275e637c56bda88379a2c6f440d47154a25 Mon Sep 17 00:00:00 2001 From: Christophe Prud'homme Date: Fri, 30 Oct 2026 12:48:53 +0100 Subject: [PATCH 3/3] fix(ci): install BibLaTeX for compiler integration Refs #20 --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20e777a..2c8e548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,14 @@ jobs: - name: Install LaTeX and PDF tools run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends latexmk texlive-latex-extra texlive-fonts-recommended biber poppler-utils + sudo apt-get install -y --no-install-recommends \ + latexmk texlive-latex-extra texlive-fonts-recommended \ + texlive-bibtex-extra biber poppler-utils + + - name: Check bibliography tools + run: | + kpsewhich biblatex.sty + biber --version - name: Verify archives and versioned PDFs with real compilers run: uv run --locked pytest tests/test_bundle.py tests/test_bundle_typst.py tests/test_paper_version.py