Add support for BLAKE2b-256 (#13204)
Some checks are pending
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## Summary

You can upload these to PyPI and `warehouse` will validate them.
This commit is contained in:
Charlie Marsh 2025-04-29 18:39:41 -04:00 committed by GitHub
parent 62bca8c34c
commit 6bce5d712f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 120 additions and 20 deletions

View file

@ -305,6 +305,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -361,6 +362,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -420,6 +422,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -476,6 +479,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -532,6 +536,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -558,7 +563,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -586,6 +591,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -595,7 +601,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -612,7 +618,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -640,6 +646,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -649,7 +656,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -770,6 +777,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -796,7 +804,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -824,6 +832,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -833,7 +842,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -879,6 +888,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -935,6 +945,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -962,7 +973,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap_err();
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
}
#[test]
@ -980,7 +991,7 @@ mod tests {
let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -1008,6 +1019,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1023,6 +1035,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1032,7 +1045,7 @@ mod tests {
},
],
}
"###);
"#);
}
/// Test for AWS Code Artifact
@ -1090,6 +1103,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1107,6 +1121,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1124,6 +1139,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: Some(
Ok(
@ -1190,6 +1206,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: Some(
Ok(
@ -1232,7 +1249,7 @@ mod tests {
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -1264,6 +1281,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1283,6 +1301,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1302,6 +1321,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1321,6 +1341,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1340,6 +1361,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1349,6 +1371,6 @@ mod tests {
},
],
}
"###);
"#);
}
}

View file

@ -23,6 +23,7 @@ uv-pypi-types = { workspace = true }
astral-tokio-tar = { workspace = true }
async-compression = { workspace = true, features = ["bzip2", "gzip", "zstd", "xz"] }
async_zip = { workspace = true }
blake2 = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
md-5 = { workspace = true }

View file

@ -1,7 +1,7 @@
use blake2::digest::consts::U32;
use sha2::Digest;
use std::pin::Pin;
use std::task::{Context, Poll};
use sha2::Digest;
use tokio::io::{AsyncReadExt, ReadBuf};
use uv_pypi_types::{HashAlgorithm, HashDigest};
@ -12,6 +12,7 @@ pub enum Hasher {
Sha256(sha2::Sha256),
Sha384(sha2::Sha384),
Sha512(sha2::Sha512),
Blake2b(blake2::Blake2b<U32>),
}
impl Hasher {
@ -21,6 +22,7 @@ impl Hasher {
Hasher::Sha256(hasher) => hasher.update(data),
Hasher::Sha384(hasher) => hasher.update(data),
Hasher::Sha512(hasher) => hasher.update(data),
Hasher::Blake2b(hasher) => hasher.update(data),
}
}
}
@ -32,6 +34,7 @@ impl From<HashAlgorithm> for Hasher {
HashAlgorithm::Sha256 => Hasher::Sha256(sha2::Sha256::new()),
HashAlgorithm::Sha384 => Hasher::Sha384(sha2::Sha384::new()),
HashAlgorithm::Sha512 => Hasher::Sha512(sha2::Sha512::new()),
HashAlgorithm::Blake2b => Hasher::Blake2b(blake2::Blake2b::new()),
}
}
}
@ -55,6 +58,10 @@ impl From<Hasher> for HashDigest {
algorithm: HashAlgorithm::Sha512,
digest: format!("{:x}", hasher.finalize()).into(),
},
Hasher::Blake2b(hasher) => HashDigest {
algorithm: HashAlgorithm::Blake2b,
digest: format!("{:x}", hasher.finalize()).into(),
},
}
}
}

View file

@ -195,6 +195,8 @@ pub struct Hashes {
pub sha384: Option<SmallString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sha512: Option<SmallString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blake2b: Option<SmallString>,
}
impl Hashes {
@ -221,24 +223,35 @@ impl Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
}),
"sha256" => Ok(Hashes {
md5: None,
sha256: Some(SmallString::from(value)),
sha384: None,
sha512: None,
blake2b: None,
}),
"sha384" => Ok(Hashes {
md5: None,
sha256: None,
sha384: Some(SmallString::from(value)),
sha512: None,
blake2b: None,
}),
"sha512" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(SmallString::from(value)),
blake2b: None,
}),
"blake2b" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(SmallString::from(value)),
}),
_ => Err(HashError::UnsupportedHashAlgorithm(fragment.to_string())),
}
@ -270,24 +283,35 @@ impl FromStr for Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
}),
"sha256" => Ok(Hashes {
md5: None,
sha256: Some(SmallString::from(value)),
sha384: None,
sha512: None,
blake2b: None,
}),
"sha384" => Ok(Hashes {
md5: None,
sha256: None,
sha384: Some(SmallString::from(value)),
sha512: None,
blake2b: None,
}),
"sha512" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(SmallString::from(value)),
blake2b: None,
}),
"blake2b" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(SmallString::from(value)),
}),
_ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())),
}
@ -315,6 +339,7 @@ pub enum HashAlgorithm {
Sha256,
Sha384,
Sha512,
Blake2b,
}
impl FromStr for HashAlgorithm {
@ -326,6 +351,7 @@ impl FromStr for HashAlgorithm {
"sha256" => Ok(Self::Sha256),
"sha384" => Ok(Self::Sha384),
"sha512" => Ok(Self::Sha512),
"blake2b" => Ok(Self::Blake2b),
_ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())),
}
}
@ -338,6 +364,7 @@ impl std::fmt::Display for HashAlgorithm {
Self::Sha256 => write!(f, "sha256"),
Self::Sha384 => write!(f, "sha384"),
Self::Sha512 => write!(f, "sha512"),
Self::Blake2b => write!(f, "blake2b"),
}
}
}
@ -503,6 +530,7 @@ impl From<HashDigests> for Hashes {
HashAlgorithm::Sha256 => hashes.sha256 = Some(digest.digest),
HashAlgorithm::Sha384 => hashes.sha384 = Some(digest.digest),
HashAlgorithm::Sha512 => hashes.sha512 = Some(digest.digest),
HashAlgorithm::Blake2b => hashes.blake2b = Some(digest.digest),
}
}
hashes
@ -551,7 +579,7 @@ pub enum HashError {
InvalidFragment(String),
#[error(
"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `{0}`"
"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `{0}`"
)]
UnsupportedHashAlgorithm(String),
}
@ -562,6 +590,21 @@ mod tests {
#[test]
fn parse_hashes() -> Result<(), HashError> {
let hashes: Hashes =
"blake2b:af4793213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a".parse()?;
assert_eq!(
hashes,
Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(
"af4793213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a".into()
),
}
);
let hashes: Hashes =
"sha512:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?;
assert_eq!(
@ -573,6 +616,7 @@ mod tests {
sha512: Some(
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
blake2b: None,
}
);
@ -586,7 +630,8 @@ mod tests {
sha384: Some(
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
sha512: None
sha512: None,
blake2b: None,
}
);
@ -600,7 +645,8 @@ mod tests {
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
sha384: None,
sha512: None
sha512: None,
blake2b: None,
}
);
@ -614,7 +660,8 @@ mod tests {
),
sha256: None,
sha384: None,
sha512: None
sha512: None,
blake2b: None,
}
);

View file

@ -4490,24 +4490,35 @@ impl From<Hash> for Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha256 => Hashes {
md5: None,
sha256: Some(value.0.digest),
sha384: None,
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha384 => Hashes {
md5: None,
sha256: None,
sha384: Some(value.0.digest),
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha512 => Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(value.0.digest),
blake2b: None,
},
HashAlgorithm::Blake2b => Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(value.0.digest),
},
}
}

View file

@ -3440,14 +3440,14 @@ fn require_hashes_unknown_algorithm() -> Result<()> {
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @r###"
.arg("--require-hashes"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `foo`
"###
error: Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `foo`
"
);
Ok(())