Reject requires python even if not listed on the index page (#13086)

Reject distributions with an incompatible `Requires-Python`, even if the
index page is missing `data-requires-python`.

Fixes #13079
This commit is contained in:
konsti 2025-04-25 12:52:02 +02:00 committed by GitHub
parent cd7621043e
commit ae5c77c0e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 93 additions and 6 deletions

View File

@ -1771,6 +1771,16 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
} }
}; };
// If there was no requires-python on the index page, we may have an incompatible
// distribution.
if let Some(requires_python) = &metadata.requires_python {
if !python_requirement.target().is_contained_by(requires_python) {
return Ok(Dependencies::Unavailable(
UnavailableVersion::RequiresPython(requires_python.clone()),
));
}
}
let requirements = self.flatten_requirements( let requirements = self.flatten_requirements(
&metadata.requires_dist, &metadata.requires_dist,
&metadata.dependency_groups, &metadata.dependency_groups,

View File

@ -11,11 +11,14 @@ use flate2::write::GzEncoder;
use fs_err::File; use fs_err::File;
use indoc::indoc; use indoc::indoc;
use url::Url; use url::Url;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::common::{download_to_disk, packse_index_url, uv_snapshot, TestContext};
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_static::EnvVars; use uv_static::EnvVars;
use crate::common::{download_to_disk, packse_index_url, uv_snapshot, TestContext};
#[test] #[test]
fn compile_requirements_in() -> Result<()> { fn compile_requirements_in() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");
@ -9508,7 +9511,7 @@ fn universal_marker_propagation() -> Result<()> {
.arg("requirements.in") .arg("requirements.in")
.arg("-p") .arg("-p")
.arg("3.8") .arg("3.8")
.arg("--universal"), @r###" .arg("--universal"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@ -9536,9 +9539,11 @@ fn universal_marker_propagation() -> Result<()> {
# via jinja2 # via jinja2
mpmath==1.3.0 mpmath==1.3.0
# via sympy # via sympy
networkx==3.2.1 networkx==3.1 ; python_full_version < '3.9'
# via torch # via torch
numpy==1.26.3 ; python_full_version < '3.9' networkx==3.2 ; python_full_version >= '3.9'
# via torch
numpy==1.24.4 ; python_full_version < '3.9'
# via torchvision # via torchvision
numpy==1.26.4 ; python_full_version >= '3.9' numpy==1.26.4 ; python_full_version >= '3.9'
# via torchvision # via torchvision
@ -9576,8 +9581,8 @@ fn universal_marker_propagation() -> Result<()> {
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead. warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead.
Resolved 25 packages in [TIME] Resolved 26 packages in [TIME]
"### "
); );
Ok(()) Ok(())
@ -17223,3 +17228,75 @@ fn pep_751_compile_no_emit_package() -> Result<()> {
Ok(()) Ok(())
} }
/// Check that we reject versions that have an incompatible `Requires-Python`, but don't
/// have a `data-requires-python` key on the index page.
#[tokio::test]
async fn index_has_no_requires_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.9", "3.12"]);
let server = MockServer::start().await;
// Unlike PyPI, https://download.pytorch.org/whl/cpu/networkx/ does not contain the
// `data-requires-python` key.
let networkx_page = r#"
<!DOCTYPE html>
<html>
<body>
<h1>Links for networkx</h1>
<a href="https://download.pytorch.org/whl/networkx-3.0-py3-none-any.whl#sha256=58058d66b1818043527244fab9d41a51fcd7dcc271748015f3c181b8a90c8e2e">networkx-3.0-py3-none-any.whl</a><br/>
<a href="https://download.pytorch.org/whl/networkx-3.2.1-py3-none-any.whl#sha256=f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2">networkx-3.2.1-py3-none-any.whl</a><br/>
<a href="https://download.pytorch.org/whl/networkx-3.3-py3-none-any.whl#sha256=28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2">networkx-3.3-py3-none-any.whl</a><br/>
</body>
</html>
"#;
Mock::given(method("GET"))
.and(path("/networkx/"))
.respond_with(ResponseTemplate::new(200).set_body_raw(networkx_page, "text/html"))
.mount(&server)
.await;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("networkx >3.0,<=3.3")?;
uv_snapshot!(context
.pip_compile()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("--python")
.arg("3.9")
.arg("--index-url")
.arg(server.uri())
.arg("requirements.in"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --python 3.9 requirements.in
networkx==3.2.1
# via -r requirements.in
----- stderr -----
Resolved 1 package in [TIME]
");
uv_snapshot!(context
.pip_compile()
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("--python")
.arg("3.12")
.arg("--index-url")
.arg(server.uri())
.arg("requirements.in"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --python 3.12 requirements.in
networkx==3.3
# via -r requirements.in
----- stderr -----
Resolved 1 package in [TIME]
");
Ok(())
}