diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f328020..2c8e548 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,6 +62,44 @@ 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 \ + 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 + build: needs: test runs-on: ubuntu-latest @@ -79,7 +117,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 4301b4b..1558a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ 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. + +### 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 ### Added @@ -220,6 +237,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..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 @@ -217,3 +227,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..3ebe756 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 @@ -442,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/ @@ -462,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 de2fde6..eebfd7d 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 @@ -9,8 +9,8 @@ asciidoc: attributes: 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' + python-supported: '3.11, 3.12, 3.13' + 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/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/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 fdd0963..5a35600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,22 +4,20 @@ 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"}, ] 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,8 +39,7 @@ dev = [ "flake8>=6.0", "mypy>=1.0,<2.0", "build>=1.2.0", - "twine>=6.2.0,<7; python_version < '3.10'", - "twine>=7.0.0; python_version >= '3.10'", + "twine>=7.0.0", "types-requests", ] @@ -55,8 +51,7 @@ dev = [ "flake8>=6.0", "mypy>=1.0,<2.0", "build>=1.2.0", - "twine>=6.2.0,<7; python_version < '3.10'", - "twine>=7.0.0; python_version >= '3.10'", + "twine>=7.0.0", "types-requests", ] @@ -82,7 +77,7 @@ include = [ [tool.black] line-length = 88 -target-version = ['py39'] +target-version = ['py311'] include = '\.pyi?$' extend-exclude = ''' /( @@ -99,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/__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..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 = {} @@ -205,6 +193,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 +424,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..69b1a78 --- /dev/null +++ b/src/article_cli/services/bundle.py @@ -0,0 +1,335 @@ +"""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) + # 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: + 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() + 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 + ) + ) + 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..420ec35 --- /dev/null +++ b/src/article_cli/services/bundle_backends.py @@ -0,0 +1,337 @@ +"""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 +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 ..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: + 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..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 @@ -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/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/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..67d3933 --- /dev/null +++ b/tests/test_bundle.py @@ -0,0 +1,311 @@ +"""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() + ) + + +@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_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_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_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/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 f7d610b..c301e61 100644 --- a/uv.lock +++ b/uv.lock @@ -1,54 +1,39 @@ version = 1 revision = 5 -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'", -] +requires-python = ">=3.11" [[package]] name = "article-cli" -version = "2.0.1" +version = "2.1.0" 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", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "twine", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { 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 = "twine" }, + { 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", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "twine", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { 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 = "twine" }, + { name = "types-requests" }, ] [package.metadata] @@ -62,9 +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 = "python_full_version >= '3.10' and extra == 'dev'", specifier = ">=7.0.0" }, - { name = "twine", marker = "python_full_version < '3.10' and extra == 'dev'", specifier = ">=6.2.0,<7" }, + { name = "twine", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "types-requests", marker = "extra == 'dev'" }, ] provides-extras = ["dev"] @@ -77,8 +60,7 @@ dev = [ { name = "mypy", specifier = ">=1.0,<2.0" }, { name = "pytest", specifier = ">=7.0" }, { name = "pytest-cov" }, - { name = "twine", marker = "python_full_version < '3.10'", specifier = ">=6.2.0,<7" }, - { name = "twine", marker = "python_full_version >= '3.10'", specifier = ">=7.0.0" }, + { name = "twine", specifier = ">=7.0.0" }, { name = "types-requests" }, ] @@ -96,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" }, @@ -129,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 = "os_name == 'nt'" }, - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli" }, -] -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 = "os_name == 'nt'" }, - { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10.2'" }, { name = "packaging" }, { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] 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 = [ @@ -189,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" }, @@ -224,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]] @@ -238,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" }, @@ -334,48 +250,13 @@ 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 = "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 = "sys_platform == 'win32'" }, ] @@ -393,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" }, -] - [[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" }, @@ -634,63 +378,12 @@ toml = [ { name = "tomli", marker = "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 = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions" }, -] -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" }, -] - [[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 = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] 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 = [ @@ -736,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" }, -] -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" @@ -767,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 = [ @@ -784,29 +464,10 @@ 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" }, -] -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" }, ] @@ -815,26 +476,10 @@ 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" }, @@ -845,37 +490,17 @@ 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" }, -] -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.12'" }, ] @@ -889,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 = [ @@ -923,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 = [ @@ -945,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" }, @@ -1022,43 +631,12 @@ 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" }, -] -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" }, ] @@ -1073,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" }, @@ -1150,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]] @@ -1181,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" }, @@ -1214,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" }, @@ -1249,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" }, ] @@ -1319,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" }, @@ -1362,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" }, @@ -1414,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 = "sys_platform == 'win32'" }, - { name = "exceptiongroup" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli" }, -] -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 = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] 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 = [ @@ -1462,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 = [ @@ -1479,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" }, @@ -1509,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" }, ] @@ -1532,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" }, @@ -1588,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]] @@ -1613,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" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" } }, -] -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" }, { name = "charset-normalizer" }, { name = "idna" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" } }, + { 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 = [ @@ -1655,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 = [ @@ -1677,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" } @@ -1686,33 +1100,12 @@ 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' or python_full_version >= '3.10'" }, - { 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" }, -] -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" } }, + { 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" } @@ -1774,79 +1167,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "twine" -version = "6.2.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 = "id" }, - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" } }, - { 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" } }, - { name = "requests-toolbelt" }, - { name = "rfc3986" }, - { name = "rich" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" } }, -] -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 = "twine" version = "7.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ { name = "id" }, { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, { name = "packaging" }, { name = "readme-renderer" }, - { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" } }, + { name = "requests" }, { name = "requests-toolbelt" }, { name = "rfc3986" }, { name = "rich" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" } }, + { name = "urllib3" }, ] 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/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.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" } }, -] -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" } -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" }, -] - [[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" } }, + { 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 = [ @@ -1862,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" },