From cbe0abcaf44dce3889d41a1dfe19bad3963ade7a Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Wed, 11 Nov 2026 02:55:31 +0530 Subject: [PATCH 1/4] fix(index): include available SHA256 hashes in wheel links Use valid release-asset digests while preserving links for assets without usable SHA256 values. Co-authored-by: GeneralTony007 <144252170+GeneralTony007@users.noreply.github.com> --- scripts/releases-to-pep-503.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/releases-to-pep-503.sh b/scripts/releases-to-pep-503.sh index 835962449..bdcf094c8 100755 --- a/scripts/releases-to-pep-503.sh +++ b/scripts/releases-to-pep-503.sh @@ -85,8 +85,12 @@ for release in $releases; do continue fi - wheel_urls=$(echo "$response" | jq -r '.assets[] | select(.name | endswith(".whl")) | .browser_download_url') - if [ -z "$wheel_urls" ]; then + wheel_assets=$(echo "$response" | jq -r ' + .assets[] | select(.name | endswith(".whl")) | + [.browser_download_url, + (if (.digest | type) == "string" then .digest else "" end)] | @tsv + ') + if [ -z "$wheel_assets" ]; then log_error "No wheel files found for release $release" continue fi @@ -95,8 +99,12 @@ for release in $releases; do release_version=$(echo "$release" | grep -oE "^[v]?[0-9]+\.[0-9]+\.[0-9]+") echo "

$release_version

" >> "$output_dir/llama-cpp-python/index.html" - echo "$wheel_urls" | while read -r asset; do - echo " $asset" >> "$output_dir/llama-cpp-python/index.html" + echo "$wheel_assets" | while IFS=$'\t' read -r asset digest; do + href="$asset" + if [[ "$digest" =~ ^sha256:([[:xdigit:]]{64})$ ]]; then + href="$asset#sha256=${BASH_REMATCH[1]}" + fi + echo " $asset" >> "$output_dir/llama-cpp-python/index.html" echo "
" >> "$output_dir/llama-cpp-python/index.html" done done From 96ce03d3ec7ade7f70ff71c27815b7f476a8294f Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Wed, 11 Nov 2026 02:56:00 +0530 Subject: [PATCH 2/4] test(index): cover release-asset digests and legacy links Exercise the real shell generator with offline release JSON fixtures, including missing and unusable digests, mixed assets, backend filters, and empty indexes. Co-authored-by: GeneralTony007 <144252170+GeneralTony007@users.noreply.github.com> --- tests/test_release_index.py | 130 ++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 tests/test_release_index.py diff --git a/tests/test_release_index.py b/tests/test_release_index.py new file mode 100644 index 000000000..347186db6 --- /dev/null +++ b/tests/test_release_index.py @@ -0,0 +1,130 @@ +"""Exercise the release-index generator without downloading release assets.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +from html.parser import HTMLParser + +import pytest + + +SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "releases-to-pep-503.sh" +WHEEL_URL = "https://example.invalid/llama_cpp_python-0.3.36-py3-none-any.whl" +SHA256 = "0123456789abcdef" * 4 + + +class Links(HTMLParser): + def __init__(self): + super().__init__() + self.hrefs = [] + + def handle_starttag(self, tag, attrs): + if tag == "a": + self.hrefs.append(dict(attrs)["href"]) + + +@pytest.fixture +def generate_index(tmp_path): + for tool in ("bash", "jq"): + if shutil.which(tool) is None: + pytest.skip(f"release-index tests require {tool}") + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + curl = bin_dir / "curl" + curl.write_text('#!/bin/sh\ncat "$RELEASE_RESPONSE"\n') + curl.chmod(0o755) + response = tmp_path / "response.json" + env = { + **os.environ, + "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", + "RELEASE_RESPONSE": str(response), + } + + def run(assets, releases="v0.3.36\n", pattern=None): + response.write_text(json.dumps({"assets": assets})) + (tmp_path / "all_releases.txt").write_text(releases) + output = tmp_path / "index" + command = ["bash", str(SCRIPT), str(output)] + if pattern is not None: + command.append(pattern) + subprocess.run(command, cwd=tmp_path, env=env, check=True, capture_output=True) + page = (output / "llama-cpp-python" / "index.html").read_text() + parser = Links() + parser.feed(page) + return parser.hrefs, page + + return run + + +def wheel(**kwargs): + return { + "name": "llama_cpp_python-0.3.36-py3-none-any.whl", + "browser_download_url": WHEEL_URL, + **kwargs, + } + + +@pytest.mark.parametrize("digest", [f"sha256:{SHA256}", f"sha256:{SHA256.upper()}"]) +def test_available_sha256_digest(generate_index, digest): + links, page = generate_index([wheel(digest=digest)]) + assert links == [f"{WHEEL_URL}#sha256={digest.split(':')[1]}"] + assert f">{WHEEL_URL}" in page + + +@pytest.mark.parametrize( + "digest", + [ + None, + "", + "sha256:abc", + "sha256:" + "z" * 64, + "sha512:" + "a" * 128, + "sha256:" + SHA256 + "extra", + 123, + False, + {}, + [], + ], +) +def test_unusable_digest_preserves_original_link(generate_index, digest): + links, _ = generate_index([wheel(digest=digest)]) + assert links == [WHEEL_URL] + + +def test_historical_asset_without_digest(generate_index): + links, _ = generate_index([wheel()]) + assert links == [WHEEL_URL] + + +def test_mixed_release_assets(generate_index): + legacy_url = WHEEL_URL.replace("py3-none-any", "cp311-cp311-linux_x86_64") + assets = [ + wheel(digest=f"sha256:{SHA256}"), + {**wheel(), "browser_download_url": legacy_url}, + { + "name": "source.tar.gz", + "browser_download_url": "https://example.invalid/source.tar.gz", + "digest": f"sha256:{SHA256}", + }, + ] + links, _ = generate_index(assets) + assert links == [f"{WHEEL_URL}#sha256={SHA256}", legacy_url] + + +def test_backend_release_pattern(generate_index): + links, page = generate_index( + [wheel(digest=f"sha256:{SHA256}")], + releases="v0.3.36\nv0.3.36-cu130\n", + pattern=r"^[v]?[0-9]+\.[0-9]+\.[0-9]+-cu130$", + ) + assert links == [f"{WHEEL_URL}#sha256={SHA256}"] + assert "

v0.3.36

" in page + + +@pytest.mark.parametrize("assets,releases", [([], "v0.3.36\n"), ([wheel()], "")]) +def test_empty_indexes_still_generate(generate_index, assets, releases): + links, page = generate_index(assets, releases=releases) + assert links == [] + assert page.rstrip().endswith("") From 78be1e2e56ac6486a71d3d564dc5f26ca138afcb Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Wed, 11 Nov 2026 02:56:29 +0530 Subject: [PATCH 3/4] docs(index): note available wheel-link SHA256 hashes Reference the maintainer's issue without claiming complete historical hash coverage. Co-authored-by: GeneralTony007 <144252170+GeneralTony007@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef9dbc67..3f8122468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- fix(index): include available SHA256 hashes in wheel links by @hakarrr (issue #1340) + - fix(ci): use supported Python versions on Ubuntu by @abetlen in #2407 - feat: update llama.cpp to ggml-org/llama.cpp@7d7f02875 From 816c045dd24170eeb0a26501675da0e9c9e980c1 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Wed, 11 Nov 2026 02:57:39 +0530 Subject: [PATCH 4/4] docs(index): link changelog entry to pull request Use the assigned pull request number in the repository's changelog format. Co-authored-by: GeneralTony007 <144252170+GeneralTony007@users.noreply.github.com> --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8122468..6f0247375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- fix(index): include available SHA256 hashes in wheel links by @hakarrr (issue #1340) - +- fix(index): include available SHA256 hashes in wheel links by @hakarrr in #2409 - fix(ci): use supported Python versions on Ubuntu by @abetlen in #2407 - feat: update llama.cpp to ggml-org/llama.cpp@7d7f02875