diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5847906..664be4b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,54 +2,32 @@ name: CI on: push: + branches: [main] + tags: ['*'] pull_request: permissions: - contents: write + contents: read jobs: + variants: + name: Read variant matrix from firmware/manifest.toml + runs-on: ubuntu-24.04 + outputs: + matrix: ${{ steps.read.outputs.matrix }} + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v8.1.0 + - id: read + working-directory: pypi + run: uv run python -m tools.emit_ci_matrix >> "$GITHUB_OUTPUT" + build: - name: Build ${{ matrix.config.board_name }} (${{ matrix.config.conf_name }}) + needs: variants + name: Build ${{ matrix.board_name }} (${{ matrix.conf_name }}) strategy: fail-fast: false - matrix: - config: - - board: nrf52840dk/nrf52840 - board_name: nrf52840dk - path: ../zephyr/samples/bluetooth/hci_usb - conf_name: default - cmake_extra_args: "" - sysbuild: false - hex_file: zephyr/zephyr.hex - - board: nrf52840dk/nrf52840 - board_name: nrf52840dk - path: ../zephyr/samples/bluetooth/hci_usb - conf_name: usbd_next - cmake_extra_args: "-DCONF_FILE=usbd_next_prj.conf" - sysbuild: false - hex_file: zephyr/zephyr.hex - - board: nrf52840dk/nrf52840 - board_name: nrf52840dk - path: ../zephyr/samples/bluetooth/hci_usb - conf_name: ACL_502 - cmake_extra_args: "-DEXTRA_CONF_FILE=$PWD/hci_usb/ACL_502.conf" - sysbuild: false - hex_file: zephyr/zephyr.hex - - board: nrf5340dk/nrf5340/cpuapp - board_name: nrf5340dk - path: ../zephyr/samples/bluetooth/hci_usb - conf_name: default - cmake_extra_args: "" - sysbuild: true - hex_file: merged.hex - - board: nrf5340dk/nrf5340/cpuapp - board_name: nrf5340dk - path: ../zephyr/samples/bluetooth/hci_usb - conf_name: ACL_502 - cmake_extra_args: "-DEXTRA_CONF_FILE=$PWD/hci_usb/ACL_502_host.conf -Dhci_ipc_EXTRA_CONF_FILE=$PWD/hci_usb/ACL_502_netcore.conf" - sysbuild: true - hex_file: merged.hex - + matrix: ${{ fromJson(needs.variants.outputs.matrix) }} runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 @@ -65,22 +43,22 @@ jobs: with: app-path: working_directory toolchains: arm-zephyr-eabi - ccache-cache-key: ${{ matrix.config.board }}-${{ matrix.config.conf_name }}-ccache + ccache-cache-key: ${{ matrix.board }}-${{ matrix.conf_name }}-ccache - working-directory: working_directory run: ./setup.sh - working-directory: working_directory run: >- - west build -b ${{ matrix.config.board }} ${{ matrix.config.path }} - ${{ matrix.config.sysbuild && '--sysbuild' || '' }} - -- ${{ matrix.config.cmake_extra_args }} + west build -b ${{ matrix.board }} ${{ matrix.path }} + ${{ matrix.sysbuild && '--sysbuild' || '' }} + -- ${{ matrix.cmake_extra_args }} - name: >- Merge hex files (workaround for https://github.com/zephyrproject-rtos/zephyr/discussions/102647, https://github.com/zephyrproject-rtos/zephyr/pull/83085) - if: matrix.config.sysbuild + if: matrix.sysbuild working-directory: working_directory run: >- python ../zephyr/scripts/build/mergehex.py @@ -89,10 +67,11 @@ jobs: -o build/merged.hex - run: | - zephyr_version=$(west list zephyr --format='{revision}') - app_name=$(basename "${{ matrix.config.path }}") - fw_name="zephyr_${app_name}_${{ matrix.config.conf_name }}_${zephyr_version}_${{ matrix.config.board_name }}.hex" - cp "build/${{ matrix.config.hex_file }}" "build/${fw_name}" + zephyr_version=$(west list zephyr --format='{revision}') # e.g. v4.4.0 + version_no_v="${zephyr_version#v}" + app_name=$(basename "${{ matrix.path }}") + fw_name="zephyr_${version_no_v}_${app_name}_${{ matrix.board_name }}_${{ matrix.conf_name }}.hex" + cp "build/${{ matrix.hex_file }}" "build/${fw_name}" echo "name=${fw_name}" >> "$GITHUB_OUTPUT" id: fw working-directory: working_directory @@ -107,6 +86,8 @@ jobs: needs: build if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: ubuntu-24.04 + permissions: + contents: write steps: - uses: actions/checkout@v6 @@ -126,3 +107,86 @@ jobs: make_latest: true target_commitish: ${{ github.sha }} files: artifacts/**/*.hex + + camas_check: + name: Lint / mypy / pytest (camas) + runs-on: ubuntu-24.04 + permissions: + contents: read + checks: write # GitHubChecks effect: per-leaf PR check entries + steps: + - uses: actions/checkout@v6 + + - uses: astral-sh/setup-uv@v8.1.0 + + - working-directory: pypi + run: uv sync + + - name: camas check + working-directory: pypi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + uv run camas check --effects='( + Summary(SummaryOptions(Fixed(100))), + GitHubChecks(GitHubChecksOptions( + sha="${{ github.event.pull_request.head.sha || github.sha }}", + )), + )' + + publish_pypi: + needs: [build, camas_check] + if: github.ref_type == 'tag' && github.ref_name != 'latest' + runs-on: ubuntu-24.04 + environment: pypi # optional manual-approval gate; remove this line if you skipped step C of the setup checklist + permissions: + contents: write # create / upload to the tag's GitHub release + id-token: write # Sigstore signing of PEP 740 attestations + attestations: write # PEP 740 attestation upload + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # hatch-vcs needs full tag history + + - uses: astral-sh/setup-uv@v8.1.0 + + - uses: actions/download-artifact@v8 + with: + path: artifacts + merge-multiple: true + + - name: Resolve PyPI package version from git tag + id: ver + run: | + # Tag is bare semver (no 'v' prefix, per the project's SemVer-strict convention). + echo "version=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + echo "Resolved PyPI version: ${GITHUB_REF_NAME}" + + - name: Build wheels (verifies SHA against firmware/manifest.toml) + working-directory: pypi + run: | + uv run python -m tools.build_fw_packages \ + --artifacts ../artifacts \ + --out ../dist \ + --version "${{ steps.ver.outputs.version }}" \ + --manifest firmware/manifest.toml + + - name: twine check + run: uv tool run --from twine twine check dist/* + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + attestations: true + packages-dir: dist/ + + - name: Create / update the tag's GitHub release + run: | + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --notes "Built from ${GITHUB_SHA}. PyPI version ${{ steps.ver.outputs.version }}." \ + dist/*.whl dist/*.tar.gz artifacts/*.hex \ + || gh release upload "${GITHUB_REF_NAME}" \ + dist/*.whl dist/*.tar.gz artifacts/*.hex --clobber + env: + GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index d4af6cc..b09489a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .venv -build* +build +build-* diff --git a/pypi/.gitignore b/pypi/.gitignore new file mode 100644 index 0000000..771d2b1 --- /dev/null +++ b/pypi/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +*.pyc +dist +*.egg-info diff --git a/pypi/.pythonversion b/pypi/.pythonversion new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/pypi/.pythonversion @@ -0,0 +1 @@ +3.14 diff --git a/pypi/README.md b/pypi/README.md new file mode 100644 index 0000000..2aa5e94 --- /dev/null +++ b/pypi/README.md @@ -0,0 +1,5 @@ +# `pypi/` + +Tooling that publishes the Zephyr HCI firmware to PyPI. + +See [`camas`](https://pypi.org/project/camas/) for tasks: `uv run camas`. diff --git a/pypi/firmware/manifest.toml b/pypi/firmware/manifest.toml new file mode 100644 index 0000000..9a9807c --- /dev/null +++ b/pypi/firmware/manifest.toml @@ -0,0 +1,52 @@ +zephyr_revision = "v4.4.0" +app = "hci_usb" + +[[firmware]] +board = "nrf52840dk" +options = "default" +board_path = "nrf52840dk/nrf52840" +sample_path = "bluetooth/hci_usb" +cmake_args = "" +sysbuild = false +hex_file = "zephyr/zephyr.hex" +sha256 = "2c1796e8e1574f74080b0c6b14699612247f58d47f4b1b569c83d7559a8166c0" + +[[firmware]] +board = "nrf52840dk" +options = "acl_502" +board_path = "nrf52840dk/nrf52840" +sample_path = "bluetooth/hci_usb" +cmake_args = "-DEXTRA_CONF_FILE={workspace}/hci_usb/ACL_502.conf" +sysbuild = false +hex_file = "zephyr/zephyr.hex" +sha256 = "ae23562e9b8030f68972e0a196f9f9680a191622897ee19cabc31cb11ed4a01d" + +[[firmware]] +board = "nrf52840dk" +options = "legacy" +board_path = "nrf52840dk/nrf52840" +sample_path = "subsys/usb/legacy/hci_usb" +cmake_args = "" +sysbuild = false +hex_file = "zephyr/zephyr.hex" +sha256 = "954230a39c2f6a426e824a0db1eaa8aa98e8f6b15228ae94c9901815fd334b80" + +[[firmware]] +board = "nrf5340dk" +options = "default" +board_path = "nrf5340dk/nrf5340/cpuapp" +sample_path = "bluetooth/hci_usb" +cmake_args = "" +sysbuild = true +hex_file = "merged.hex" +sha256 = "6ebe341f018ccb6a8b7040001b86dbb7866edf32420736fdc1c3562c1aa979cf" + +[[firmware]] +board = "nrf5340dk" +options = "acl_502" +board_path = "nrf5340dk/nrf5340/cpuapp" +sample_path = "bluetooth/hci_usb" +cmake_args = "-DEXTRA_CONF_FILE={workspace}/hci_usb/ACL_502_host.conf -Dhci_ipc_EXTRA_CONF_FILE={workspace}/hci_usb/ACL_502_netcore.conf" +sysbuild = true +hex_file = "merged.hex" +sha256 = "43dbde167996bc9ed9beb295d9259157351a911e7cef9aa2c1dbd3812eecc48f" diff --git a/pypi/pyproject.toml b/pypi/pyproject.toml new file mode 100644 index 0000000..72bd826 --- /dev/null +++ b/pypi/pyproject.toml @@ -0,0 +1,55 @@ +[project] +name = "zephyr_hci_tools" +description = "Build tooling for the intercreate/zephyr-hci PyPI packages. Not published." +requires-python = ">=3.14" +dynamic = ["version"] +dependencies = ["hatchling", "hatch-vcs", "tomli-w"] + +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.version.raw-options] +root = ".." +local_scheme = "no-local-version" + +[tool.hatch.build.targets.wheel] +packages = ["tools"] + +[dependency-groups] +dev = ["camas[github_checks]", "mypy", "ruff", "pytest", "twine", "build"] + +[tool.mypy] +strict = true +python_version = "3.14" +files = ["tools", "tests", "tasks.py"] +warn_unreachable = true +enable_error_code = [ +"redundant-expr", +"truthy-bool", +"ignore-without-code", +"unused-awaitable", +] + +[tool.ruff] +target-version = "py314" +line-length = 120 + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP", "SIM", "RUF", "PL"] +ignore = ["PLR0913"] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["PLR2004"] # magic-value comparisons are fine in assertions + +[tool.ruff.format] +quote-style = "double" + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = ["slow: end-to-end tests that build venvs and install wheels (slow)"] + +# Tasks live in tasks.py (camas: 'tasks.py wins over pyproject.toml'). diff --git a/pypi/tasks.py b/pypi/tasks.py new file mode 100644 index 0000000..6994b5a --- /dev/null +++ b/pypi/tasks.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import tomllib +from pathlib import Path + +from camas import Parallel, Sequential, Task + +_PYPI: Path = Path(__file__).resolve().parent +_MANIFEST: Path = _PYPI / "firmware" / "manifest.toml" + + +def _load_variant_tags() -> tuple[str, ...]: + data = tomllib.loads(_MANIFEST.read_text()) + return tuple(f"{e['board']}:{e['options']}" for e in data["firmware"]) + + +_VARIANT_TAGS: tuple[str, ...] = _load_variant_tags() + +build = Parallel( + Task("python -m tools.build_firmware --variant {VARIANT}", cwd=_PYPI), + matrix={"VARIANT": _VARIANT_TAGS}, + name="build", +) + +_refresh_manifest = Task( + "python -m tools.update_manifest --artifacts ../build-hexes", + name="refresh_manifest", + cwd=_PYPI, +) + +update = Sequential(build, _refresh_manifest, name="update") + +update_manifest = Task("python -m tools.update_manifest", cwd=_PYPI) + +wheels = Task( + "python -m tools.build_fw_packages --artifacts ../build-hexes --out ../dist --version 0.0.0.dev0", + name="wheels", + cwd=_PYPI, +) + +format_check = Task("ruff format --check .", cwd=_PYPI) +lint = Task("ruff check .", cwd=_PYPI) +mypy = Task("mypy tools tests tasks.py", cwd=_PYPI) +test = Task("pytest", cwd=_PYPI) +check = Parallel(format_check, lint, mypy, test, name="check") diff --git a/pypi/tests/__init__.py b/pypi/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pypi/tests/conftest.py b/pypi/tests/conftest.py new file mode 100644 index 0000000..b3e4b07 --- /dev/null +++ b/pypi/tests/conftest.py @@ -0,0 +1,84 @@ +"""Shared fixtures: synthetic .hex artifacts + matching manifest in a tmp directory.""" + +from __future__ import annotations + +import hashlib +import textwrap +from pathlib import Path +from typing import NamedTuple + +import pytest + + +class FixtureSet(NamedTuple): + """Generated test inputs for one build run.""" + + artifact_dir: Path + manifest_path: Path + expected_distribution_names: tuple[str, ...] + + +SYNTHETIC_VARIANTS: tuple[tuple[str, str], ...] = ( + ("nrf52840dk", "default"), + ("nrf52840dk", "acl_502"), + ("nrf52840dk", "legacy"), + ("nrf5340dk", "default"), + ("nrf5340dk", "acl_502"), +) + +FIXTURE_ZEPHYR_REVISION = "v4.4.0" +FIXTURE_APP = "hci_usb" + + +def _synthetic_hex_bytes(board: str, options: str) -> bytes: + return f"; synthetic test hex for {board} / {options}\n".encode() + b"\xff" * 1024 + + +@pytest.fixture +def fixture_set(tmp_path: Path) -> FixtureSet: + artifact_dir = tmp_path / "artifacts" + artifact_dir.mkdir() + + version_slug = FIXTURE_ZEPHYR_REVISION.removeprefix("v") + dist_names: list[str] = [] + manifest_rows: list[str] = [] + + for board, options in SYNTHETIC_VARIANTS: + body = _synthetic_hex_bytes(board, options) + sha = hashlib.sha256(body).hexdigest() + filename = f"zephyr_{version_slug}_{FIXTURE_APP}_{board}_{options}.hex" + (artifact_dir / filename).write_bytes(body) + dist_names.append(f"zephyr_{version_slug}_{FIXTURE_APP}_{board}_{options}") + manifest_rows.append( + textwrap.dedent( + f"""\ + [[firmware]] + board = "{board}" + options = "{options}" + board_path = "{board}/synthetic" + sample_path = "synthetic/{options}" + cmake_args = "" + sysbuild = false + hex_file = "zephyr/zephyr.hex" + sha256 = "{sha}" + """ + ) + ) + + manifest_path = tmp_path / "manifest.toml" + manifest_path.write_text( + textwrap.dedent( + f"""\ + zephyr_revision = "{FIXTURE_ZEPHYR_REVISION}" + app = "{FIXTURE_APP}" + + """ + ) + + "\n".join(manifest_rows) + ) + + return FixtureSet( + artifact_dir=artifact_dir, + manifest_path=manifest_path, + expected_distribution_names=tuple(dist_names), + ) diff --git a/pypi/tests/test_build_fw_packages.py b/pypi/tests/test_build_fw_packages.py new file mode 100644 index 0000000..0a2b5a5 --- /dev/null +++ b/pypi/tests/test_build_fw_packages.py @@ -0,0 +1,327 @@ +"""Unit-ish tests for tools/build_fw_packages.py (no venv install, no network).""" + +from __future__ import annotations + +import hashlib +import subprocess +from pathlib import Path + +import pytest + +from tests.conftest import FIXTURE_APP, FIXTURE_ZEPHYR_REVISION, FixtureSet +from tools._model import MissingArtifact, ShaMismatch, SiblingSpec, UmbrellaSpec +from tools.build_fw_packages import ( + build_umbrella_spec, + distribution_name, + import_name, + load_manifest, + main, + umbrella_distribution_name, + verify_sha, +) + + +def test_load_manifest_round_trips(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + assert manifest.zephyr_revision == FIXTURE_ZEPHYR_REVISION + assert manifest.app == FIXTURE_APP + assert manifest.version_slug == "4.4.0" + assert len(manifest.entries) == 5 + + +def test_load_manifest_rejects_non_tag_revision(tmp_path: Path) -> None: + bad = tmp_path / "manifest.toml" + bad.write_text('zephyr_revision = "abc123def"\napp = "hci_usb"\nfirmware = []\n') + with pytest.raises(ValueError, match="not a tag-shaped"): + load_manifest(bad) + + +def test_distribution_and_import_names(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + entry = manifest.entries[0] + dist = distribution_name(manifest, entry) + assert dist == f"zephyr_4.4.0_hci_usb_{entry.board}_{entry.options}" + assert import_name(dist) == dist.replace(".", "_") + + +def test_umbrella_distribution_name(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + assert umbrella_distribution_name(manifest) == "zephyr_4.4.0_hci" + + +def test_verify_sha_ok(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + result = verify_sha( + manifest=manifest, + entry=manifest.entries[0], + artifact_dir=fixture_set.artifact_dir, + package_version="0.0.1", + ) + assert isinstance(result, SiblingSpec) + assert result.package_version == "0.0.1" + assert result.hex_path.is_file() + + +def test_verify_sha_mismatch(fixture_set: FixtureSet, tmp_path: Path) -> None: + manifest = load_manifest(fixture_set.manifest_path) + entry = manifest.entries[0] + artifact_path = fixture_set.artifact_dir / f"zephyr_4.4.0_hci_usb_{entry.board}_{entry.options}.hex" + artifact_path.write_bytes(b"tampered") + result = verify_sha( + manifest=manifest, + entry=entry, + artifact_dir=fixture_set.artifact_dir, + package_version="0.0.1", + ) + assert isinstance(result, ShaMismatch) + assert result.expected == entry.sha256 + assert result.actual == hashlib.sha256(b"tampered").hexdigest() + + +def test_verify_sha_missing_artifact(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + entry = manifest.entries[0] + artifact_path = fixture_set.artifact_dir / f"zephyr_4.4.0_hci_usb_{entry.board}_{entry.options}.hex" + artifact_path.unlink() + result = verify_sha( + manifest=manifest, + entry=entry, + artifact_dir=fixture_set.artifact_dir, + package_version="0.0.1", + ) + assert isinstance(result, MissingArtifact) + assert result.expected_path == artifact_path + assert result.entry == entry + + +def test_main_aborts_on_missing_artifact( + fixture_set: FixtureSet, tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + next(fixture_set.artifact_dir.iterdir()).unlink() + + out_dir = tmp_path / "dist" + exit_code = main( + [ + "--artifacts", + str(fixture_set.artifact_dir), + "--out", + str(out_dir), + "--manifest", + str(fixture_set.manifest_path), + "--version", + "0.0.1", + "--work-dir", + str(tmp_path / "work"), + ] + ) + assert exit_code == 1 + captured = capsys.readouterr() + assert "missing:" in captured.err + + +def test_build_umbrella_spec(fixture_set: FixtureSet) -> None: + manifest = load_manifest(fixture_set.manifest_path) + siblings = tuple( + verify_sha( + manifest=manifest, + entry=entry, + artifact_dir=fixture_set.artifact_dir, + package_version="0.0.1", + ) + for entry in manifest.entries + ) + assert all(isinstance(s, SiblingSpec) for s in siblings) + spec = build_umbrella_spec( + manifest=manifest, + package_version="0.0.1", + siblings=tuple(s for s in siblings if isinstance(s, SiblingSpec)), + ) + assert isinstance(spec, UmbrellaSpec) + assert spec.distribution_name == "zephyr_4.4.0_hci" + assert tuple(s.entry.board for s in spec.siblings) == tuple(e.board for e in manifest.entries) + + +def test_main_builds_all_six_packages(fixture_set: FixtureSet, tmp_path: Path) -> None: + out_dir = tmp_path / "dist" + work_dir = tmp_path / "work" + exit_code = main( + [ + "--artifacts", + str(fixture_set.artifact_dir), + "--out", + str(out_dir), + "--manifest", + str(fixture_set.manifest_path), + "--version", + "0.0.1", + "--work-dir", + str(work_dir), + ] + ) + assert exit_code == 0 + + wheels = sorted(out_dir.glob("*.whl")) + sdists = sorted(out_dir.glob("*.tar.gz")) + assert len(wheels) == 6, f"expected 6 wheels, got {[w.name for w in wheels]}" + assert len(sdists) == 6 + + expected_stems = {name.replace(".", "_") for name in (*fixture_set.expected_distribution_names, "zephyr_4.4.0_hci")} + actual_stems = {w.name.split("-")[0] for w in wheels} + assert actual_stems == expected_stems + + +def test_main_aborts_on_sha_mismatch(fixture_set: FixtureSet, tmp_path: Path) -> None: + artifact = next(fixture_set.artifact_dir.iterdir()) + artifact.write_bytes(b"tampered") + + out_dir = tmp_path / "dist" + exit_code = main( + [ + "--artifacts", + str(fixture_set.artifact_dir), + "--out", + str(out_dir), + "--manifest", + str(fixture_set.manifest_path), + "--version", + "0.0.1", + "--work-dir", + str(tmp_path / "work"), + ] + ) + assert exit_code == 1 + assert not list(out_dir.glob("*.whl")) or len(list(out_dir.glob("*.whl"))) < 6 + + +@pytest.mark.slow +def test_built_packages_install_and_import(fixture_set: FixtureSet, tmp_path: Path) -> None: + """End-to-end: build 6 wheels, install into a fresh venv, exercise the registry.""" + out_dir = tmp_path / "dist" + assert ( + main( + [ + "--artifacts", + str(fixture_set.artifact_dir), + "--out", + str(out_dir), + "--manifest", + str(fixture_set.manifest_path), + "--version", + "0.0.1", + "--work-dir", + str(tmp_path / "work"), + ] + ) + == 0 + ) + + venv_dir = tmp_path / "venv" + subprocess.run(["uv", "venv", str(venv_dir)], check=True) + wheels = sorted(out_dir.glob("*.whl")) + subprocess.run( + ["uv", "pip", "install", "--python", str(venv_dir / "bin" / "python"), *map(str, wheels)], + check=True, + ) + + venv_python = venv_dir / "bin" / "python" + + smoke_script = tmp_path / "smoke.py" + smoke_script.write_text( + """ +import hashlib +import zephyr_4_4_0_hci as umbrella +import zephyr_4_4_0_hci_usb_nrf52840dk_default as fw + +assert fw.HEX_PATH.is_file() +assert fw.HEX_PATH.suffix == ".hex" +assert hashlib.sha256(fw.HEX_PATH.read_bytes()).hexdigest() == fw.HEX_SHA256 +assert fw.read_firmware_bytes() == fw.HEX_PATH.read_bytes() +assert fw.ZEPHYR_REVISION == "v4.4.0" +assert fw.BOARD == "nrf52840dk" +assert fw.OPTIONS == "default" + +assert umbrella.firmware.nrf52840dk_default is fw +assert umbrella.firmware.nrf52840dk_default.HEX_PATH == fw.HEX_PATH +assert umbrella.firmware.nrf52840dk_default.HEX_SHA256 == fw.HEX_SHA256 + +modules = tuple(umbrella.firmware) +assert len(modules) == 5, f"expected 5 siblings, got {len(modules)}: {modules}" +for m in modules: + assert m.ZEPHYR_REVISION == "v4.4.0" + assert m.HEX_PATH.is_file() + +named = umbrella.firmware._asdict() +assert "nrf52840dk_default" in named +assert "nrf52840dk_legacy" in named +assert "nrf5340dk_acl_502" in named + +print("OK") +""" + ) + result = subprocess.run( + [str(venv_python), str(smoke_script)], + capture_output=True, + text=True, + check=True, + ) + assert "OK" in result.stdout, result.stdout + result.stderr + + sibling_cli = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci_usb_nrf52840dk_default"], + capture_output=True, + text=True, + check=True, + ) + sibling_path = Path(sibling_cli.stdout.strip()) + assert sibling_path.is_file() + assert sibling_path.suffix == ".hex" + assert sibling_path.name == "firmware.hex" + + umbrella_cli = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci"], + capture_output=True, + text=True, + check=True, + ) + rows = [line.split("\t") for line in umbrella_cli.stdout.strip().splitlines()] + assert len(rows) == 5, rows + assert all(len(r) == 5 for r in rows), rows + apps_boards_options = {(r[0], r[1], r[2]) for r in rows} + assert ("hci_usb", "nrf52840dk", "default") in apps_boards_options + assert ("hci_usb", "nrf52840dk", "legacy") in apps_boards_options + + umbrella_help = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci", "--help"], + capture_output=True, + text=True, + check=True, + ) + for variant in ("nrf52840dk_default", "nrf52840dk_legacy", "nrf5340dk_acl_502"): + assert variant in umbrella_help.stdout, umbrella_help.stdout + + umbrella_specific = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci", "nrf52840dk_default"], + capture_output=True, + text=True, + check=True, + ) + assert Path(umbrella_specific.stdout.strip()) == sibling_path + + umbrella_unknown = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci", "no_such_variant"], + capture_output=True, + text=True, + check=False, + ) + assert umbrella_unknown.returncode != 0 + assert "invalid choice" in umbrella_unknown.stderr, umbrella_unknown.stderr + + sibling_path.write_bytes(b"tampered") + tampered = subprocess.run( + [str(venv_python), "-m", "zephyr_4_4_0_hci_usb_nrf52840dk_default"], + capture_output=True, + text=True, + check=False, + ) + assert tampered.returncode == 2, tampered + assert "SHA-256 mismatch" in tampered.stderr, tampered.stderr diff --git a/pypi/tools/__init__.py b/pypi/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pypi/tools/_model.py b/pypi/tools/_model.py new file mode 100644 index 0000000..04f3f92 --- /dev/null +++ b/pypi/tools/_model.py @@ -0,0 +1,109 @@ +"""Immutable data model for the firmware-packaging tools. + +All build state flows through these NamedTuples. No mutable globals, no inheritance. +Build outcomes are a sum type (BuildResult) discriminated by NamedTuple identity. +""" + +from pathlib import Path +from typing import NamedTuple + + +class ManifestEntry(NamedTuple): + """One (board, options) row from firmware/manifest.toml. + + Carries everything needed to both *build* the firmware (board_path, cmake_args, + sysbuild, hex_file) and *verify* it (sha256). Single source of truth for variants. + """ + + board: str + """Short board name used in PyPI distribution names and canonical hex filenames (e.g. "nrf52840dk").""" + + options: str + """Lowercased options slug (e.g. "default", "acl_502", "legacy").""" + + board_path: str + """Full west `--board` value (e.g. "nrf52840dk/nrf52840", "nrf5340dk/nrf5340/cpuapp").""" + + sample_path: str + """Path relative to zephyr/samples/ for this variant's sample (e.g. "bluetooth/hci_usb").""" + + cmake_args: str + """Trailing args after `--` in `west build`. Empty string if none.""" + + sysbuild: bool + """If True, build with `--sysbuild` and run mergehex.py before collecting the hex.""" + + hex_file: str + """Path inside the build directory to the .hex we want (e.g. "zephyr/zephyr.hex" or "merged.hex").""" + + sha256: str + """Expected SHA-256 of the built .hex (lowercase hex). Refreshed by update_manifest after a rebuild.""" + + +class Manifest(NamedTuple): + """Top-level parse of firmware/manifest.toml.""" + + zephyr_revision: str + app: str + entries: tuple[ManifestEntry, ...] + + @property + def version_slug(self) -> str: + """Zephyr revision with leading `v` stripped (e.g. `v4.4.0` -> `4.4.0`).""" + return self.zephyr_revision.removeprefix("v") + + +class SiblingSpec(NamedTuple): + """All data needed to render one board+options firmware package.""" + + entry: ManifestEntry + distribution_name: str + import_name: str + app: str + package_version: str + zephyr_revision: str + hex_path: Path + + +class UmbrellaSpec(NamedTuple): + """All data needed to render the aggregate registry package. + + Carries the full `SiblingSpec` for every variant so the rendered umbrella can emit + direct, statically-typed imports of each sibling rather than runtime entry-point lookup. + """ + + distribution_name: str + import_name: str + package_version: str + zephyr_revision: str + siblings: tuple[SiblingSpec, ...] + + +type BuildSpec = SiblingSpec | UmbrellaSpec + + +class ShaMismatch(NamedTuple): + """SHA-256 of the on-disk hex did not match firmware/manifest.toml.""" + + entry: ManifestEntry + expected: str + actual: str + artifact_path: Path + + +class MissingArtifact(NamedTuple): + """The expected .hex file is not present in the artifacts directory.""" + + entry: ManifestEntry + expected_path: Path + + +class BuildOk(NamedTuple): + """Wheel + sdist produced for a spec.""" + + spec: BuildSpec + wheel: Path + sdist: Path + + +type BuildResult = BuildOk | ShaMismatch | MissingArtifact diff --git a/pypi/tools/_templates.py b/pypi/tools/_templates.py new file mode 100644 index 0000000..24a7d3a --- /dev/null +++ b/pypi/tools/_templates.py @@ -0,0 +1,306 @@ +"""String templates for generated package files. + +Templates use str.format_map with explicit placeholder names. No Jinja, no f-strings at +render time -- the source-of-truth shape lives here in plain Python strings so a +reviewer can read and grep them. +""" + +from typing import Final + +SIBLING_PYPROJECT: Final[str] = """\ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{distribution_name}" +version = "{package_version}" +description = "Prebuilt Zephyr {app} firmware for {board} ({options}) -- Zephyr {zephyr_revision}" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.10" +authors = [{{ name = "Intercreate, Inc.", email = "jp@intercreate.io" }}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: System :: Hardware", +] +keywords = ["bluetooth", "ble", "hci", "zephyr", "{board}", "firmware", "bumble"] + +[project.urls] +Homepage = "https://github.com/intercreate/zephyr-hci" +"Source firmware" = "https://github.com/intercreate/zephyr-hci/releases" +"zephyr-revision" = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/{zephyr_revision}" +"firmware-sha256" = "https://github.com/intercreate/zephyr-hci/blob/main/firmware/manifest.toml" + +[tool.hatch.build] +include = ["src/{import_name}/**"] + +[tool.hatch.build.targets.wheel] +packages = ["src/{import_name}"] +""" + + +SIBLING_INIT: Final[str] = '''\ +"""Prebuilt Zephyr {app} firmware for {board} ({options}) -- Zephyr {zephyr_revision}.""" + +from hashlib import sha256 +from importlib.resources import files +from pathlib import Path +from typing import Final + +HEX_PATH: Final[Path] = Path(str(files(__name__) / "firmware.hex")) +"""Absolute path to the bundled .hex firmware.""" + +HEX_SHA256: Final[str] = "{sha256}" +"""Expected SHA-256 of firmware.hex (lowercase hex).""" + +ZEPHYR_REVISION: Final[str] = "{zephyr_revision}" +"""Zephyr RTOS tag this firmware was built from.""" + +APP: Final[str] = "{app}" +"""Zephyr sample app, e.g. 'hci_usb'.""" + +BOARD: Final[str] = "{board}" +"""Target board, e.g. 'nrf52840dk'.""" + +OPTIONS: Final[str] = "{options}" +"""Build options slug, e.g. 'default', 'acl_502', 'legacy'.""" + + +def read_firmware_bytes() -> bytes: + """Read the firmware, verifying SHA-256 against the embedded expected value.""" + data = HEX_PATH.read_bytes() + actual = sha256(data).hexdigest() + if actual != HEX_SHA256: + raise ValueError( + f"firmware.hex SHA-256 mismatch: expected {{HEX_SHA256}}, got {{actual}}" + ) + return data +''' + + +SIBLING_MAIN: Final[str] = '''\ +"""CLI: verify the bundled firmware on disk and print its absolute path. + +Usage: + python -m {import_name} + +Exits 0 with the path on stdout. On failure, prints a diagnostic to stderr and exits non-zero: + 1 — firmware.hex is missing from the installed package (corrupted install?) + 2 — firmware.hex SHA-256 does not match the value embedded at build time (tampered?) +""" + +import sys + +from . import HEX_PATH, read_firmware_bytes + + +def main() -> int: + if not HEX_PATH.is_file(): + print( + f"{{__package__}}: firmware.hex not found at expected location {{HEX_PATH}}", + file=sys.stderr, + ) + return 1 + try: + _ = read_firmware_bytes() + except ValueError as exc: + print(f"{{__package__}}: {{exc}}", file=sys.stderr) + return 2 + print(HEX_PATH) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +''' + + +UMBRELLA_MAIN: Final[str] = '''\ +"""CLI: list every prebuilt firmware variant, or print one variant's verified .hex path. + +Usage: + python -m {import_name} # list every variant (tab-separated) + python -m {import_name} # path to 's firmware.hex (after SHA verify) + +`python -m {import_name} --help` enumerates every valid . +""" + +import argparse +import sys + +from . import firmware + + +def main() -> int: + parser = argparse.ArgumentParser( + prog="python -m {import_name}", + description="List every prebuilt firmware variant, or print one variant's verified .hex path.", + ) + parser.add_argument( + "variant", + nargs="?", + choices=firmware._fields, + help="Variant to print the verified .hex path for. Omit to list every variant.", + ) + args = parser.parse_args() + + if args.variant is None: + for fw in firmware: + print(f"{{fw.APP}}\\t{{fw.BOARD}}\\t{{fw.OPTIONS}}\\t{{fw.HEX_PATH}}\\t{{fw.HEX_SHA256}}") + return 0 + + fw = getattr(firmware, args.variant) + try: + _ = fw.read_firmware_bytes() + except FileNotFoundError as exc: + print(f"{{args.variant}}: firmware.hex missing: {{exc}}", file=sys.stderr) + return 1 + except ValueError as exc: + print(f"{{args.variant}}: {{exc}}", file=sys.stderr) + return 2 + print(fw.HEX_PATH) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +''' + + +SIBLING_README: Final[str] = """\ +# {distribution_name} + +Prebuilt Zephyr `{app}` firmware for `{board}` (`{options}`) built from Zephyr `{zephyr_revision}`. + +```python +import {import_name} as fw + +print(fw.HEX_PATH) # Path to firmware.hex on disk +print(fw.HEX_SHA256) # expected SHA-256 +print(fw.ZEPHYR_REVISION) # "{zephyr_revision}" + +data = fw.read_firmware_bytes() # raises ValueError if the file has been tampered with +``` + +Built and published from . +""" + + +UMBRELLA_PYPROJECT: Final[str] = """\ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{distribution_name}" +version = "{package_version}" +description = "Aggregate registry of all Intercreate prebuilt Zephyr HCI firmware ({zephyr_revision})" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.10" +authors = [{{ name = "Intercreate, Inc.", email = "info@intercreate.io" }}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: System :: Hardware", +] +keywords = ["bluetooth", "ble", "hci", "zephyr", "firmware", "bumble"] +dependencies = [ +{dependencies_block}\ +] + +[project.urls] +Homepage = "https://github.com/intercreate/zephyr-hci" +"zephyr-revision" = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/{zephyr_revision}" + +[tool.hatch.build] +include = ["src/{import_name}/**"] + +[tool.hatch.build.targets.wheel] +packages = ["src/{import_name}"] +""" + + +UMBRELLA_INIT: Final[str] = '''\ +"""Typed access to every prebuilt Zephyr HCI firmware variant for Zephyr {zephyr_revision}. + +`dependencies` pins each sibling, so the module imports below are statically valid. +`firmware` is a NamedTuple whose fields *are* the sibling modules themselves — access +each variant by attribute (IDE autocomplete + full mypy/pyright resolution of the +per-sibling typed constants). Iterate `firmware` to walk every variant; use +`firmware._asdict()` if you also need the field names. +""" + +from pathlib import Path +from typing import Final, NamedTuple, Protocol + +{sibling_imports} + + +class FirmwareModule(Protocol): + """Structural type satisfied by every sibling firmware module.""" + + APP: str + BOARD: str + OPTIONS: str + ZEPHYR_REVISION: str + HEX_PATH: Path + HEX_SHA256: str + + def read_firmware_bytes(self) -> bytes: ... + + +class Firmware(NamedTuple): + """Every prebuilt firmware variant, keyed by `_`.""" + +{firmware_fields} + + +ZEPHYR_REVISION: Final[str] = "{zephyr_revision}" + +firmware: Final[Firmware] = Firmware( +{firmware_args} +) +''' + + +UMBRELLA_README: Final[str] = """\ +# {distribution_name} + +Typed access to every Intercreate-prebuilt Zephyr HCI firmware variant for Zephyr `{zephyr_revision}`. + +`pip install {distribution_name}` pulls in every sibling firmware package as a hard dependency. +The umbrella exposes `firmware: Firmware` — a `NamedTuple` whose fields are the sibling modules themselves, +so attribute access gives you full IDE autocomplete and static typing. + +```python +from {import_name} import firmware + +# Direct typed access (no string keys, IDE autocompletes the variant name): +hex_path = firmware.nrf52840dk_default.HEX_PATH +data = firmware.nrf5340dk_acl_502.read_firmware_bytes() + +# Iterate every variant (NamedTuple is a tuple): +for fw in firmware: + print(fw.BOARD, fw.OPTIONS, fw.HEX_PATH) + +# Or with field names: +for name, fw in firmware._asdict().items(): + print(name, fw.HEX_PATH) +``` + +Shell CLI — `python -m {import_name} --help` lists every variant: + +```sh +HEX=$(python -m {import_name} nrf52840dk_default) && nrfjprog --program "$HEX" +python -m {import_name} # tab-separated: \\t\\t\\t\\t +``` + +Built and published from . +""" diff --git a/pypi/tools/build_firmware.py b/pypi/tools/build_firmware.py new file mode 100644 index 0000000..42ff1a6 --- /dev/null +++ b/pypi/tools/build_firmware.py @@ -0,0 +1,138 @@ +"""Build a single Zephyr firmware variant declared in firmware/manifest.toml. + +Usage: + python -m tools.build_firmware --variant nrf52840dk:default + +The variant tag is `:`. Steps: + +1. Look up the matching [[firmware]] row in firmware/manifest.toml. +2. Run `west build -b --build-dir [--sysbuild] -- `. +3. If `sysbuild = true`, run `mergehex.py` to produce `merged.hex`. +4. Copy the resulting hex (manifest's `hex_file`) into a shared collection dir under the + canonical name `zephyr____.hex`. + +`{workspace}` in `cmake_args` is substituted with the absolute path to the zephyr-hci +workspace root, so the manifest can stay portable. +""" + +from __future__ import annotations + +import argparse +import shlex +import shutil +import subprocess +from pathlib import Path + +from tools._model import Manifest, ManifestEntry +from tools.build_fw_packages import load_manifest + + +def find_variant(manifest: Manifest, board: str, options: str) -> ManifestEntry: + for entry in manifest.entries: + if entry.board == board and entry.options == options: + return entry + available = ", ".join(f"{e.board}:{e.options}" for e in manifest.entries) + raise SystemExit(f"No variant {board}:{options} in manifest. Available: {available}") + + +def _zephyr_python() -> str: + """Locate the Python interpreter that has Zephyr's build deps installed. + + Under `uv run camas`, plain `python` resolves to the pypi/.venv interpreter + (which doesn't have intelhex / pyelftools / etc.). The Zephyr workspace venv + is where the user installed scripts/requirements.txt; `west` lives in its + bin/ dir, so the sibling `python` is the right one for mergehex et al. + """ + west_path = shutil.which("west") + if west_path is None: + raise SystemExit("west not found on PATH — activate the Zephyr workspace venv first") + return str(Path(west_path).resolve().parent / "python") + + +def build_variant(manifest: Manifest, entry: ManifestEntry, workspace: Path, out_dir: Path) -> Path: + build_dir = workspace / f"build-{entry.board}-{entry.options}" + sample = workspace.parent / "zephyr" / "samples" / entry.sample_path + + cmake_args = entry.cmake_args.replace("{workspace}", str(workspace)) + + west_cmd: list[str] = [ + "west", + "build", + "-b", + entry.board_path, + str(sample), + "--build-dir", + str(build_dir), + ] + if entry.sysbuild: + west_cmd.append("--sysbuild") + if cmake_args: + west_cmd.append("--") + west_cmd.extend(shlex.split(cmake_args)) + subprocess.run(west_cmd, check=True) + + if entry.sysbuild: + subprocess.run( + [ + _zephyr_python(), + str(workspace.parent / "zephyr" / "scripts" / "build" / "mergehex.py"), + str(build_dir / "hci_usb" / "zephyr" / "zephyr.hex"), + str(build_dir / "hci_ipc" / "zephyr" / "zephyr.hex"), + "-o", + str(build_dir / "merged.hex"), + ], + check=True, + ) + + src = build_dir / entry.hex_file + canonical = out_dir / f"zephyr_{manifest.version_slug}_{manifest.app}_{entry.board}_{entry.options}.hex" + out_dir.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src, canonical) + return canonical + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + p = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None) + p.add_argument( + "--variant", + required=True, + help="Variant tag `:` (e.g. `nrf52840dk:default`).", + ) + p.add_argument( + "--manifest", + type=Path, + default=Path(__file__).resolve().parent.parent / "firmware" / "manifest.toml", + ) + p.add_argument( + "--workspace", + type=Path, + default=Path(__file__).resolve().parent.parent.parent, + help="zephyr-hci workspace root (default: parent of pypi/).", + ) + p.add_argument( + "--out-dir", + type=Path, + default=None, + help="Where to deposit the canonical-named hex (default: /build-hexes).", + ) + return p.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + if ":" not in args.variant: + raise SystemExit(f"--variant must be :, got {args.variant!r}") + board, options = args.variant.split(":", 1) + + manifest = load_manifest(args.manifest) + entry = find_variant(manifest, board, options) + workspace = args.workspace.resolve() + out_dir = (args.out_dir or (workspace / "build-hexes")).resolve() + + canonical = build_variant(manifest, entry, workspace, out_dir) + print(f"Built {canonical}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pypi/tools/build_fw_packages.py b/pypi/tools/build_fw_packages.py new file mode 100644 index 0000000..eebeb3d --- /dev/null +++ b/pypi/tools/build_fw_packages.py @@ -0,0 +1,336 @@ +"""Build all PyPI packages from firmware/manifest.toml + a directory of .hex artifacts. + +Pipeline (pure functions, composed): + + load_manifest(path) -> Manifest + artifact_filename(manifest, e) -> str + distribution_name(manifest, e) -> str + import_name(distribution_name) -> str + verify_sha(entry, artifact_dir) -> SiblingSpec | ShaMismatch + render_package(spec, work_dir) -> Path (writes files, no build) + run_uv_build(pkg_dir, out_dir) -> (wheel, sdist) + main() collects results, prints a summary, exits 1 if any ShaMismatch. + +The umbrella package is built last with `dependencies = [...]` populated from the +sibling distribution names just verified. +""" + +from __future__ import annotations + +import argparse +import hashlib +import re +import shutil +import subprocess +import sys +import tomllib +from pathlib import Path +from typing import Final, assert_never + +from tools._model import ( + BuildOk, + BuildResult, + BuildSpec, + Manifest, + ManifestEntry, + MissingArtifact, + ShaMismatch, + SiblingSpec, + UmbrellaSpec, +) +from tools._templates import ( + SIBLING_INIT, + SIBLING_MAIN, + SIBLING_PYPROJECT, + SIBLING_README, + UMBRELLA_INIT, + UMBRELLA_MAIN, + UMBRELLA_PYPROJECT, + UMBRELLA_README, +) + +ZEPHYR_TAG_RE: Final[re.Pattern[str]] = re.compile(r"^v\d+\.\d+\.\d+(?:[.\-]\w+)?$") + + +def load_manifest(path: Path) -> Manifest: + with path.open("rb") as f: + raw = tomllib.load(f) + zephyr_revision = str(raw["zephyr_revision"]) + if not ZEPHYR_TAG_RE.match(zephyr_revision): + raise ValueError( + f"manifest.zephyr_revision {zephyr_revision!r} is not a tag-shaped string " + f"(expected like 'v4.4.0'). Releasing from a non-tag would produce a " + f"non-reproducible package name." + ) + entries = tuple( + ManifestEntry( + board=str(row["board"]), + options=str(row["options"]), + board_path=str(row["board_path"]), + sample_path=str(row["sample_path"]), + cmake_args=str(row["cmake_args"]), + sysbuild=bool(row["sysbuild"]), + hex_file=str(row["hex_file"]), + sha256=str(row["sha256"]).lower(), + ) + for row in raw["firmware"] + ) + return Manifest( + zephyr_revision=zephyr_revision, + app=str(raw["app"]), + entries=entries, + ) + + +def distribution_name(manifest: Manifest, entry: ManifestEntry) -> str: + return f"zephyr_{manifest.version_slug}_{manifest.app}_{entry.board}_{entry.options}" + + +def umbrella_distribution_name(manifest: Manifest) -> str: + """Transport-agnostic umbrella name. Sample app (e.g. `hci_usb`) lives only in sibling names; + the umbrella covers the whole HCI category so future non-USB HCI variants land in the same package.""" + return f"zephyr_{manifest.version_slug}_hci" + + +def import_name(dist_name: str) -> str: + return dist_name.replace(".", "_") + + +def artifact_filename(manifest: Manifest, entry: ManifestEntry) -> str: + return f"{distribution_name(manifest, entry)}.hex" + + +def verify_sha( + *, + manifest: Manifest, + entry: ManifestEntry, + artifact_dir: Path, + package_version: str, +) -> SiblingSpec | ShaMismatch | MissingArtifact: + hex_path = artifact_dir / artifact_filename(manifest, entry) + if not hex_path.is_file(): + return MissingArtifact(entry=entry, expected_path=hex_path) + actual = hashlib.sha256(hex_path.read_bytes()).hexdigest() + if actual != entry.sha256: + return ShaMismatch( + entry=entry, + expected=entry.sha256, + actual=actual, + artifact_path=hex_path, + ) + dist = distribution_name(manifest, entry) + return SiblingSpec( + entry=entry, + distribution_name=dist, + import_name=import_name(dist), + app=manifest.app, + package_version=package_version, + zephyr_revision=manifest.zephyr_revision, + hex_path=hex_path, + ) + + +def build_umbrella_spec( + *, + manifest: Manifest, + package_version: str, + siblings: tuple[SiblingSpec, ...], +) -> UmbrellaSpec: + dist = umbrella_distribution_name(manifest) + return UmbrellaSpec( + distribution_name=dist, + import_name=import_name(dist), + package_version=package_version, + zephyr_revision=manifest.zephyr_revision, + siblings=siblings, + ) + + +def _variant_attr(sibling: SiblingSpec) -> str: + """Python identifier for a sibling's attribute on the umbrella's `Firmware` NamedTuple.""" + return f"{sibling.entry.board}_{sibling.entry.options}" + + +def _sibling_module_alias(sibling: SiblingSpec) -> str: + return f"_{_variant_attr(sibling)}" + + +def _render_sibling_imports(siblings: tuple[SiblingSpec, ...]) -> str: + return "\n".join(f"import {s.import_name} as {_sibling_module_alias(s)}" for s in siblings) + + +def _render_firmware_fields(siblings: tuple[SiblingSpec, ...]) -> str: + return "\n".join(f" {_variant_attr(s)}: FirmwareModule" for s in siblings) + + +def _render_firmware_args(siblings: tuple[SiblingSpec, ...]) -> str: + return "\n".join(f" {_variant_attr(s)}={_sibling_module_alias(s)}," for s in siblings) + + +def _render_sibling(spec: SiblingSpec, work_dir: Path) -> Path: + pkg_dir = work_dir / spec.distribution_name + src_dir = pkg_dir / "src" / spec.import_name + src_dir.mkdir(parents=True, exist_ok=True) + + fields = { + "distribution_name": spec.distribution_name, + "import_name": spec.import_name, + "app": spec.app, + "board": spec.entry.board, + "options": spec.entry.options, + "package_version": spec.package_version, + "zephyr_revision": spec.zephyr_revision, + "sha256": spec.entry.sha256, + } + (pkg_dir / "pyproject.toml").write_text(SIBLING_PYPROJECT.format_map(fields)) + (pkg_dir / "README.md").write_text(SIBLING_README.format_map(fields)) + (src_dir / "__init__.py").write_text(SIBLING_INIT.format_map(fields)) + (src_dir / "__main__.py").write_text(SIBLING_MAIN.format_map(fields)) + (src_dir / "py.typed").write_text("") + shutil.copyfile(spec.hex_path, src_dir / "firmware.hex") + return pkg_dir + + +def _render_umbrella(spec: UmbrellaSpec, work_dir: Path) -> Path: + pkg_dir = work_dir / spec.distribution_name + src_dir = pkg_dir / "src" / spec.import_name + src_dir.mkdir(parents=True, exist_ok=True) + + dependencies_block = "".join(f' "{s.distribution_name}=={spec.package_version}",\n' for s in spec.siblings) + fields = { + "distribution_name": spec.distribution_name, + "import_name": spec.import_name, + "package_version": spec.package_version, + "zephyr_revision": spec.zephyr_revision, + "dependencies_block": dependencies_block, + "sibling_imports": _render_sibling_imports(spec.siblings), + "firmware_fields": _render_firmware_fields(spec.siblings), + "firmware_args": _render_firmware_args(spec.siblings), + } + (pkg_dir / "pyproject.toml").write_text(UMBRELLA_PYPROJECT.format_map(fields)) + (pkg_dir / "README.md").write_text(UMBRELLA_README.format_map(fields)) + (src_dir / "__init__.py").write_text(UMBRELLA_INIT.format_map(fields)) + (src_dir / "__main__.py").write_text(UMBRELLA_MAIN.format_map(fields)) + (src_dir / "py.typed").write_text("") + return pkg_dir + + +def render_package(spec: BuildSpec, work_dir: Path) -> Path: + match spec: + case SiblingSpec(): + return _render_sibling(spec, work_dir) + case UmbrellaSpec(): + return _render_umbrella(spec, work_dir) + case _: + assert_never(spec) + + +def run_uv_build(pkg_dir: Path, out_dir: Path, *, package_version: str) -> tuple[Path, Path]: + out_dir.mkdir(parents=True, exist_ok=True) + subprocess.run( + ["uv", "build", "--wheel", "--sdist", "--out-dir", str(out_dir.resolve())], + cwd=pkg_dir, + check=True, + ) + normalized = pkg_dir.name.replace(".", "_") + wheel = out_dir / f"{normalized}-{package_version}-py3-none-any.whl" + sdist = out_dir / f"{normalized}-{package_version}.tar.gz" + if not wheel.is_file() or not sdist.is_file(): + raise RuntimeError( + f"uv build for {pkg_dir.name!r} did not produce expected files in {out_dir}: " + f"wheel={wheel.is_file()}, sdist={sdist.is_file()}" + ) + return wheel, sdist + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + p = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None) + p.add_argument("--artifacts", type=Path, required=True, help="Directory containing the input .hex files.") + p.add_argument("--out", type=Path, required=True, help="Output directory for built wheels + sdists.") + p.add_argument( + "--version", + required=True, + help="PyPI package version to inject (NOT the Zephyr version). Typically supplied by hatch-vcs.", + ) + p.add_argument( + "--manifest", + type=Path, + default=Path(__file__).resolve().parent.parent / "firmware" / "manifest.toml", + help="Path to firmware/manifest.toml (default: %(default)s).", + ) + p.add_argument( + "--work-dir", + type=Path, + default=Path("build/packages"), + help="Scratch directory for rendered package sources (default: %(default)s).", + ) + return p.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + manifest = load_manifest(args.manifest) + + if args.work_dir.exists(): + shutil.rmtree(args.work_dir) + args.work_dir.mkdir(parents=True) + + sibling_results: list[BuildResult] = [] + sibling_spec_list: list[SiblingSpec] = [] + for entry in manifest.entries: + result = verify_sha( + manifest=manifest, + entry=entry, + artifact_dir=args.artifacts, + package_version=args.version, + ) + match result: + case ShaMismatch() | MissingArtifact(): + sibling_results.append(result) + case SiblingSpec() as spec: + sibling_spec_list.append(spec) + pkg_dir = render_package(spec, args.work_dir) + wheel, sdist = run_uv_build(pkg_dir, args.out, package_version=args.version) + sibling_results.append(BuildOk(spec=spec, wheel=wheel, sdist=sdist)) + case _: + assert_never(result) + + missing = [r for r in sibling_results if isinstance(r, MissingArtifact)] + mismatches = [r for r in sibling_results if isinstance(r, ShaMismatch)] + if missing or mismatches: + print("\nArtifact verification failed -- aborting before umbrella build:", file=sys.stderr) + for r in missing: + print( + f" missing: {r.expected_path} (variant {r.entry.board}:{r.entry.options})", + file=sys.stderr, + ) + for m in mismatches: + print( + f" SHA mismatch: {m.artifact_path.name} (expected {m.expected}, got {m.actual})", + file=sys.stderr, + ) + return 1 + + umbrella_spec = build_umbrella_spec( + manifest=manifest, + package_version=args.version, + siblings=tuple(sibling_spec_list), + ) + pkg_dir = render_package(umbrella_spec, args.work_dir) + wheel, sdist = run_uv_build(pkg_dir, args.out, package_version=args.version) + umbrella_result: BuildResult = BuildOk(spec=umbrella_spec, wheel=wheel, sdist=sdist) + + print("\nBuilt packages:") + for summary_result in (*sibling_results, umbrella_result): + match summary_result: + case BuildOk(spec=built_spec, wheel=w, sdist=s): + print(f" {built_spec.distribution_name}: {w.name}, {s.name}") + case ShaMismatch() | MissingArtifact(): + pass + case _: + assert_never(summary_result) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pypi/tools/emit_ci_matrix.py b/pypi/tools/emit_ci_matrix.py new file mode 100644 index 0000000..fd0d38b --- /dev/null +++ b/pypi/tools/emit_ci_matrix.py @@ -0,0 +1,53 @@ +"""Emit firmware/manifest.toml as a GitHub Actions matrix include array. + +Used by ci.yaml's `variants` prelude job so the build matrix is data-driven from +the same manifest that drives local `camas build` — adding a variant is a single +edit to firmware/manifest.toml. + +Output: one line `matrix=` on stdout, suitable for appending to $GITHUB_OUTPUT. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from tools.build_fw_packages import load_manifest + + +def matrix_include(manifest_path: Path) -> list[dict[str, object]]: + manifest = load_manifest(manifest_path) + return [ + { + "board": entry.board_path, + "board_name": entry.board, + "path": f"../zephyr/samples/{entry.sample_path}", + "conf_name": entry.options, + "cmake_extra_args": entry.cmake_args.replace("{workspace}", "$PWD"), + "sysbuild": entry.sysbuild, + "hex_file": entry.hex_file, + } + for entry in manifest.entries + ] + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + p = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None) + p.add_argument( + "--manifest", + type=Path, + default=Path(__file__).resolve().parent.parent / "firmware" / "manifest.toml", + ) + return p.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + matrix = {"include": matrix_include(args.manifest)} + print(f"matrix={json.dumps(matrix)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pypi/tools/update_manifest.py b/pypi/tools/update_manifest.py new file mode 100644 index 0000000..fab663a --- /dev/null +++ b/pypi/tools/update_manifest.py @@ -0,0 +1,137 @@ +"""Refresh the `sha256` fields in pypi/firmware/manifest.toml from a directory of built .hex artifacts. + +Defaults assume the workspace layout in this repo (`pypi/tools/`, `pypi/firmware/manifest.toml`, +artifacts collected at `/build-hexes/`). The `--manifest` default is anchored to +this module's location so the script works the same regardless of cwd. The local +`camas update` task invokes: + + cd pypi && uv run python -m tools.update_manifest --artifacts ../build-hexes + +The manifest's variant list (board, options, board_path, cmake_args, sysbuild, hex_file) +is the single source of truth and is preserved as-is. For each [[firmware]] entry, we +look up `zephyr____.hex` in the artifacts dir, recompute +the SHA-256, and write the manifest back. Variants missing from the artifacts dir cause +a non-zero exit — partial updates would silently drift. + +`--zephyr-revision` and `--app` overrides exist only for unusual cases (e.g. moving to a +new Zephyr version: edit the manifest header first, then run this with a matching +override to confirm the artifacts match). Default behavior is to use whatever the +manifest itself declares. +""" + +from __future__ import annotations + +import argparse +import hashlib +import sys +from pathlib import Path + +import tomli_w + +from tools._model import Manifest, ManifestEntry +from tools.build_fw_packages import load_manifest + + +def _entry_to_dict(entry: ManifestEntry) -> dict[str, str | bool]: + return { + "board": entry.board, + "options": entry.options, + "board_path": entry.board_path, + "sample_path": entry.sample_path, + "cmake_args": entry.cmake_args, + "sysbuild": entry.sysbuild, + "hex_file": entry.hex_file, + "sha256": entry.sha256, + } + + +def _expected_hex_filename(manifest: Manifest, entry: ManifestEntry) -> str: + return f"zephyr_{manifest.version_slug}_{manifest.app}_{entry.board}_{entry.options}.hex" + + +def refresh_entry(manifest: Manifest, entry: ManifestEntry, artifact_dir: Path) -> ManifestEntry | None: + """Return a new ManifestEntry with sha256 recomputed from the matching .hex, or None if absent.""" + hex_path = artifact_dir / _expected_hex_filename(manifest, entry) + if not hex_path.is_file(): + return None + sha = hashlib.sha256(hex_path.read_bytes()).hexdigest() + return entry._replace(sha256=sha) + + +def refresh_manifest(manifest: Manifest, artifact_dir: Path) -> tuple[Manifest, tuple[str, ...]]: + """Recompute SHAs for every entry. Returns the new manifest and a list of missing artifact filenames.""" + refreshed: list[ManifestEntry] = [] + missing: list[str] = [] + for entry in manifest.entries: + new_entry = refresh_entry(manifest, entry, artifact_dir) + if new_entry is None: + missing.append(_expected_hex_filename(manifest, entry)) + refreshed.append(entry) + else: + refreshed.append(new_entry) + return (manifest._replace(entries=tuple(refreshed)), tuple(missing)) + + +def write_manifest(path: Path, manifest: Manifest) -> None: + payload: dict[str, object] = { + "zephyr_revision": manifest.zephyr_revision, + "app": manifest.app, + "firmware": [_entry_to_dict(e) for e in manifest.entries], + } + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("wb") as f: + tomli_w.dump(payload, f) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + p = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None) + p.add_argument("--artifacts", type=Path, required=True) + p.add_argument( + "--manifest", + type=Path, + default=Path(__file__).resolve().parent.parent / "firmware" / "manifest.toml", + help="Path to pypi/firmware/manifest.toml (default: %(default)s).", + ) + p.add_argument( + "--zephyr-revision", + default=None, + help="Override the manifest's zephyr_revision before SHA refresh (advanced).", + ) + p.add_argument( + "--app", + default=None, + help="Override the manifest's app before SHA refresh (advanced).", + ) + return p.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + manifest = load_manifest(args.manifest) + + if args.zephyr_revision is not None: + manifest = manifest._replace(zephyr_revision=args.zephyr_revision) + if args.app is not None: + manifest = manifest._replace(app=args.app) + + refreshed, missing = refresh_manifest(manifest, args.artifacts) + + if missing: + print(f"Missing {len(missing)} artifact(s) in {args.artifacts}:", file=sys.stderr) + for name in missing: + print(f" {name}", file=sys.stderr) + print( + "All variants in the manifest must have a corresponding .hex in --artifacts. Aborting without writing.", + file=sys.stderr, + ) + return 1 + + write_manifest(args.manifest, refreshed) + print(f"Refreshed {len(refreshed.entries)} SHAs in {args.manifest}:") + for entry in refreshed.entries: + print(f" {entry.board:>14} / {entry.options:<12} {entry.sha256}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pypi/uv.lock b/pypi/uv.lock new file mode 100644 index 0000000..a003195 --- /dev/null +++ b/pypi/uv.lock @@ -0,0 +1,825 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version < '3.15'", +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "camas" +version = "0.1.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/fa/53c38a7b0c766f39a03893a41b438654e5b28cfa32efabb77534fcb629ae/camas-0.1.8.tar.gz", hash = "sha256:e8ee46c5062fa11d42911256896f012ee280dccd3a0ae2b306275266a293a9f5", size = 59618, upload-time = "2026-05-16T23:18:32.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/3d/2887ddde5a9875030029cf045054435168ffbdd1315fe0f4c55a23bf9dfb/camas-0.1.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d38b67e81b4e91dcd87b3ec10e9787224f521826beaeeb8a89e984792bf54e2d", size = 409585, upload-time = "2026-05-16T23:18:23.835Z" }, + { url = "https://files.pythonhosted.org/packages/81/a5/21be048df7899607ea3ec79077fb10309ce32f7db40272b5fbc18924488f/camas-0.1.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34ed5b3dcebe5447d01da5fbac2e1ea23a3e1dd43e12d2834bd1840f5acb440c", size = 616367, upload-time = "2026-05-16T23:18:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/a64500bc69808bd00ec6c68a959b037191e1f04f69ae079d4c39a222d07f/camas-0.1.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba57fadb5ea2087dc25ebc704361a2fd3f5326d16e3feed6452cc64bf3788040", size = 637151, upload-time = "2026-05-16T23:18:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/18/7d/9e9336218542e6d225ff78498fd9136f137942aab57c84c775cea2eddadf/camas-0.1.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3b0662a859420edd0085dd363ec90f76f3161be9f57c6706dc21859eb3947ee2", size = 625092, upload-time = "2026-05-16T23:18:27.924Z" }, + { url = "https://files.pythonhosted.org/packages/84/0b/bb09464187094d484d812d4a9c0e6a1391de735e3dd443e83e0b5b52f478/camas-0.1.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf5b4af5e2dbed2c2130da2afa356042c5a08b55617def69a4bc89f266c2be26", size = 635344, upload-time = "2026-05-16T23:18:29.443Z" }, + { url = "https://files.pythonhosted.org/packages/42/51/badc2ebb4edfa1423c4f57fcc4c7444ba47328bc31ed7ee575188a661b30/camas-0.1.8-cp314-cp314-win_amd64.whl", hash = "sha256:d845ef62a05c09b03fdfa4716d8d6b59014a403d02439ffbaab6997bd1f7bcf5", size = 337237, upload-time = "2026-05-16T23:18:30.75Z" }, +] + +[package.optional-dependencies] +github-checks = [ + { name = "httpx" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { 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" }, +] + +[[package]] +name = "charset-normalizer" +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/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { 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/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 = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +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 = "cryptography" +version = "48.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +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 = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hatch-vcs" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hatchling" }, + { name = "setuptools-scm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/4cc743d38adbee9d57d786fa496ed1daadb17e48589b6da8fa55717a0746/hatch_vcs-0.5.0.tar.gz", hash = "sha256:0395fa126940340215090c344a2bf4e2a77bcbe7daab16f41b37b98c95809ff9", size = 11424, upload-time = "2025-05-27T05:16:04.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/48/1f85cee4b7b4f40b9b814b1febbc661bda6ced9649e410a0b74f6e415dd0/hatch_vcs-0.5.0-py3-none-any.whl", hash = "sha256:b49677dbdc597460cc22d01b27ab3696f5e16a21ecf2700fb01bc28e1f2a04a7", size = 8507, upload-time = "2025-05-27T05:16:03.184Z" }, +] + +[[package]] +name = "hatchling" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "id" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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 = [ + { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, +] + +[[package]] +name = "idna" +version = "3.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +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" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { 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 = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +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 = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nh3" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/5f/1d19bdc7d27238e37f3672cdc02cb77c56a4a86d140cd4f4f23c90df6e16/nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", size = 20743, upload-time = "2026-04-25T10:44:16.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/b0/8587ac42a9627ab88e7e221601f1dfccbf4db80b2a29222ea63266dc9abc/nh3-0.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", size = 1420126, upload-time = "2026-04-25T10:43:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/1dbc4d0c43f12e8c1784ede17eaee6f061d4fbe5505757c65c49b2ceab95/nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", size = 793943, upload-time = "2026-04-25T10:43:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/47/9f/d6758d7a14ee964bf439cc35ae4fa24a763a93399c8ef6f22bd11d532d29/nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", size = 841150, upload-time = "2026-04-25T10:43:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/d5d1ae8374612c98f390e1ea7c610fa6c9716259a03bbf4d15b269f40073/nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", size = 1008415, upload-time = "2026-04-25T10:43:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/d13a9c3fd2d9c131a2a281737380e9379eb0f8c33fea24c2b923aaafbb15/nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", size = 1092706, upload-time = "2026-04-25T10:43:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/bb/57/2f3add7f8680fcc896afa6a675cb2bab09982853ee8af40bad621f6b61c4/nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", size = 1048346, upload-time = "2026-04-25T10:43:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/2f9e4ffa82863074d1361bfe949bc46393d91b3411579dfbbd090b24cac5/nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", size = 1029038, upload-time = "2026-04-25T10:43:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/e8/10/2804deb3f3315184c9cae41702e293c87524b5a21f766b07d7fe3ffbcfbb/nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", size = 603263, upload-time = "2026-04-25T10:43:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/f6685248b49f7548fc9a8c335ab3a52f68610b72e8a61576447151e4e2e6/nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", size = 616866, upload-time = "2026-04-25T10:43:51.005Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/d8c9018635d4acfefde6b68470daa510eed715a350cbaa2f928ba0609f81/nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", size = 602566, upload-time = "2026-04-25T10:43:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", size = 1442393, upload-time = "2026-04-25T10:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/072120d506978ab053e1732d0efa7c86cb478fee0ee098fda0ac0d31cb34/nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", size = 837722, upload-time = "2026-04-25T10:43:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/d4e06e28c5ad1c4b065f89737d02631bd49f1660b6ebcf17a87ffcd201da/nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", size = 822872, upload-time = "2026-04-25T10:43:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/0a/62/50659255213f241ec5797ae7427464c969397373e83b3659372b341ae869/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", size = 1100031, upload-time = "2026-04-25T10:43:58.098Z" }, + { url = "https://files.pythonhosted.org/packages/00/7a/a12ae77593b2fcf3be25df7bc1c01967d0de448bdb4b6c7ec80fe4f5a74f/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4", size = 1057669, upload-time = "2026-04-25T10:43:59.328Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/5647dc04c0233192a3956fc91708822b21403a06508cacf78083c68e7bf0/nh3-0.3.5-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b", size = 914795, upload-time = "2026-04-25T10:44:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1", size = 806976, upload-time = "2026-04-25T10:44:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/85/01/26761e1dc2b848e65a62c19e5d39ad446283287cd4afddc89f364ab86bc9/nh3-0.3.5-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc", size = 834904, upload-time = "2026-04-25T10:44:03.454Z" }, + { url = "https://files.pythonhosted.org/packages/33/53/0766113e679540ac1edc1b82b1295aecd321eeb75d6fead70109a838b6ee/nh3-0.3.5-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b", size = 857159, upload-time = "2026-04-25T10:44:05.003Z" }, + { url = "https://files.pythonhosted.org/packages/58/36/734d353dfaf292fed574b8b3092f0ef79dc6404f3879f7faaa61a4701fad/nh3-0.3.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4", size = 1018600, upload-time = "2026-04-25T10:44:06.18Z" }, + { url = "https://files.pythonhosted.org/packages/6b/aa/d9c59c1b49669fcb7bababa55df82385f029ad5c2651f583c3a1141cfdd1/nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", size = 1103530, upload-time = "2026-04-25T10:44:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/cdd210bfb8d9d43fb02fc3c868336b9955934d8e15e66eb1d15a147b8af0/nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", size = 1061754, upload-time = "2026-04-25T10:44:09.362Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/7a39e72e668c8445bdd95e494b3e21cfdddc68329be8ea3522c8befb46c4/nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", size = 1040938, upload-time = "2026-04-25T10:44:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/4c/fc2f9ed208a3801a319f59b5fea03cdc20cf3bd8af14be930d3a8de01224/nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", size = 611445, upload-time = "2026-04-25T10:44:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/db/1a/e4c9b5e2ae13e6092c9ec16d8ca30646cb01fcdea245f36c5b08fd21fbd5/nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", size = 626502, upload-time = "2026-04-25T10:44:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +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 = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +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" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +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 = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "readme-renderer" +version = "44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } +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.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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 = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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" } +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 = "ruff" +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "setuptools-scm" +version = "10.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "vcs-versioning" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/b1/2a6a8ecd6f9e263754036a0b573360bdbd6873b595725e49e11139722041/setuptools_scm-10.0.5.tar.gz", hash = "sha256:bbba8fe754516cdefd017f4456721775e6ef9662bd7887fb52ae26813d4838c3", size = 56748, upload-time = "2026-03-27T15:57:05.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/e1/342c4434df56aa537f6ce7647eefee521d96fbb828b08acd709865767652/setuptools_scm-10.0.5-py3-none-any.whl", hash = "sha256:f611037d8aae618221503b8fa89319f073438252ae3420e01c9ceec249131a0a", size = 21695, upload-time = "2026-03-27T15:57:03.969Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2026.5.22.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/b6/1c41aa221b157b624ea1a72e975404ef228724d249011ee411ac211a615e/trove_classifiers-2026.5.22.10.tar.gz", hash = "sha256:5477e9974e91904fb2cfa4a7581ab6e2f30c2c38d847fd00ed866080748101d5", size = 17061, upload-time = "2026-05-22T10:17:28.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl", hash = "sha256:01fe864225726e03efb843827ecabfe319fc4dee8dd66d65b8996cb09be46e2c", size = 14225, upload-time = "2026-05-22T10:17:27.569Z" }, +] + +[[package]] +name = "twine" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "id" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, + { name = "packaging" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +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 = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +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.7.0" +source = { registry = "https://pypi.org/simple" } +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" }, +] + +[[package]] +name = "vcs-versioning" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/42/d97a7795055677961c63a1eef8e7b19d5968ed992ed3a70ab8eb012efad8/vcs_versioning-1.1.1.tar.gz", hash = "sha256:fabd75a3cab7dd8ac02fe24a3a9ba936bf258667b5a62ed468c9a1da0f5775bc", size = 97575, upload-time = "2026-03-27T20:42:41.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/60/73603fbcdbe5e803855bcce4414f94eaeed449083bd8183e67161af78188/vcs_versioning-1.1.1-py3-none-any.whl", hash = "sha256:b541e2ba79fc6aaa3850f8a7f88af43d97c1c80649c01142ee4146eddbc599e4", size = 79851, upload-time = "2026-03-27T20:42:40.45Z" }, +] + +[[package]] +name = "zephyr-hci-tools" +source = { editable = "." } +dependencies = [ + { name = "hatch-vcs" }, + { name = "hatchling" }, + { name = "tomli-w" }, +] + +[package.dev-dependencies] +dev = [ + { name = "build" }, + { name = "camas", extra = ["github-checks"] }, + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "twine" }, +] + +[package.metadata] +requires-dist = [ + { name = "hatch-vcs" }, + { name = "hatchling" }, + { name = "tomli-w" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "build" }, + { name = "camas", extras = ["github-checks"] }, + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "twine" }, +] diff --git a/west.yml b/west.yml index 2640204..3daa481 100644 --- a/west.yml +++ b/west.yml @@ -2,7 +2,7 @@ manifest: projects: - name: zephyr url: https://github.com/zephyrproject-rtos/zephyr - revision: v4.2.2 + revision: v4.4.0 import: name-allowlist: - cmsis_6