Enforce `requires-python` in `pylock.toml` (#14787)

## Summary

Turns out we weren't validating this at install-time.
This commit is contained in:
Charlie Marsh 2025-07-21 10:37:14 -04:00 committed by GitHub
parent d768dedff6
commit aafeda2253
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 68 additions and 1 deletions

View File

@ -186,7 +186,7 @@ pub struct PylockToml {
lock_version: Version,
created_by: String,
#[serde(skip_serializing_if = "Option::is_none")]
requires_python: Option<RequiresPython>,
pub requires_python: Option<RequiresPython>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub extras: Vec<ExtraName>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]

View File

@ -444,6 +444,17 @@ pub(crate) async fn pip_install(
format!("Not a valid `pylock.toml` file: {}", pylock.user_display())
})?;
// Verify that the Python version is compatible with the lock file.
if let Some(requires_python) = lock.requires_python.as_ref() {
if !requires_python.contains(interpreter.python_version()) {
return Err(anyhow::anyhow!(
"The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`",
interpreter.python_version(),
requires_python,
));
}
}
// Convert the extras and groups specifications into a concrete form.
let extras = extras.with_defaults(DefaultExtras::default());
let extras = extras

View File

@ -382,6 +382,17 @@ pub(crate) async fn pip_sync(
format!("Not a valid `pylock.toml` file: {}", pylock.user_display())
})?;
// Verify that the Python version is compatible with the lock file.
if let Some(requires_python) = lock.requires_python.as_ref() {
if !requires_python.contains(interpreter.python_version()) {
return Err(anyhow::anyhow!(
"The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`",
interpreter.python_version(),
requires_python,
));
}
}
// Convert the extras and groups specifications into a concrete form.
let extras = extras.with_defaults(DefaultExtras::default());
let extras = extras

View File

@ -11590,6 +11590,51 @@ requires_python = "==3.13.*"
Ok(())
}
#[test]
fn pep_751_requires_python() -> Result<()> {
let context = TestContext::new_with_versions(&["3.12", "3.13"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = ["iniconfig"]
"#,
)?;
context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();
context
.venv()
.arg("--python")
.arg("3.12")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install()
.arg("--preview")
.arg("-r")
.arg("pylock.toml"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: The requested interpreter resolved to Python 3.12.[X], which is incompatible with the `pylock.toml`'s Python requirement: `>=3.13`
"
);
Ok(())
}
/// Test that uv doesn't hang if an index returns a distribution for the wrong package.
#[tokio::test]
async fn bogus_redirect() -> Result<()> {