Skip to content

Commit

Permalink
Check version compatibility for uv python pin without args
Browse files Browse the repository at this point in the history
  • Loading branch information
blueraft committed Jul 11, 2024
1 parent 33ca6ee commit f8cfdcf
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 36 deletions.
86 changes: 57 additions & 29 deletions crates/uv/src/commands/python/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ pub(crate) async fn pin(
// Display the current pinned Python version
if let Some(pins) = requests_from_version_file().await? {
for pin in pins {
let python = match PythonInstallation::find(
&pin,
EnvironmentPreference::OnlySystem,
python_preference,
cache,
) {
Ok(python) => Some(python),
// If no matching Python version is found, don't fail unless `resolved` was requested
Err(uv_python::Error::MissingPython(err)) if !resolved => {
warn_user_once!("{}", err);
None
}
Err(err) => return Err(err.into()),
};
if !isolated {
if let Some(ref python) = python {
assert_python_compatibility(python).await?;
}
}
writeln!(printer.stdout(), "{}", pin.to_canonical_string())?;
}
return Ok(ExitStatus::Success);
Expand All @@ -59,35 +78,8 @@ pub(crate) async fn pin(
};

if !isolated {
if let Ok(project) = VirtualProject::discover(&std::env::current_dir()?, None).await {
let (requires_python, project_type) = match project {
VirtualProject::Project(project_workspace) => {
debug!(
"Discovered project `{}` at: {}",
project_workspace.project_name(),
project_workspace.workspace().install_path().display()
);
let requires_python = find_requires_python(project_workspace.workspace())?;
(requires_python, "project")
}
VirtualProject::Virtual(workspace) => {
debug!(
"Discovered virtual workspace at: {}",
workspace.install_path().display()
);
let requires_python = find_requires_python(&workspace)?;
(requires_python, "virtual")
}
};
let python_version = python
.as_ref()
.map(uv_python::PythonInstallation::python_version);
if let (Some(requires_python), Some(python_version)) = (requires_python, python_version)
{
if !requires_python.contains(python_version) {
anyhow::bail!("The pinned Python version is incompatible with the {project_type}'s `Requires-Python` of {requires_python}.");
}
}
if let Some(ref python) = python {
assert_python_compatibility(python).await?;
}
}

Expand Down Expand Up @@ -117,3 +109,39 @@ pub(crate) async fn pin(

Ok(ExitStatus::Success)
}

/// Checks if the pinned Python version is compatible with the workspace/project's `Requires-Python`.
async fn assert_python_compatibility(python: &uv_python::PythonInstallation) -> Result<()> {
if let Ok(project) = VirtualProject::discover(&std::env::current_dir()?, None).await {
let (requires_python, project_type) = match project {
VirtualProject::Project(project_workspace) => {
debug!(
"Discovered project `{}` at: {}",
project_workspace.project_name(),
project_workspace.workspace().install_path().display()
);
let requires_python = find_requires_python(project_workspace.workspace())?;
(requires_python, "project")
}
VirtualProject::Virtual(workspace) => {
debug!(
"Discovered virtual workspace at: {}",
workspace.install_path().display()
);
let requires_python = find_requires_python(&workspace)?;
(requires_python, "virtual")
}
};

if let Some(requires_python) = requires_python {
if !requires_python.contains(python.python_version()) {
anyhow::bail!(
"The pinned Python version is incompatible with the {}'s `Requires-Python` of {}.",
project_type,
requires_python
);
}
}
}
Ok(())
}
53 changes: 46 additions & 7 deletions crates/uv/tests/python_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,32 +232,32 @@ fn python_pin_no_python() {

#[test]
fn python_pin_compatible_with_requires_python() -> anyhow::Result<()> {
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
let context: TestContext = TestContext::new_with_versions(&["3.10", "3.11"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
requires-python = ">=3.11"
dependencies = ["iniconfig"]
"#,
)?;

uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
uv_snapshot!(context.filters(), context.python_pin().arg("3.10"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: The pinned Python version is incompatible with the project's `Requires-Python` of >=3.12.
error: The pinned Python version is incompatible with the project's `Requires-Python` of >=3.11.
"###);

uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r###"
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
success: true
exit_code: 0
----- stdout -----
Pinned to `3.12`
Pinned to `3.11`
----- stderr -----
"###);
Expand All @@ -268,9 +268,48 @@ fn python_pin_compatible_with_requires_python() -> anyhow::Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(python_version, @r###"
3.12
3.11
"###);
});

// Updating `requires-python` should affect `uv python pin` compatibilities.
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
"#,
)?;

uv_snapshot!(context.filters(), context.python_pin(), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: The pinned Python version is incompatible with the project's `Requires-Python` of >=3.12.
"###);

Ok(())
}

#[test]
fn warning_pinned_python_version_not_installed() -> anyhow::Result<()> {
let context: TestContext = TestContext::new_with_versions(&["3.10", "3.11"]);
let python_version_file = context.temp_dir.child(PYTHON_VERSION_FILENAME);
python_version_file.write_str(r"3.12")?;
uv_snapshot!(context.filters(), context.python_pin(), @r###"
success: true
exit_code: 0
----- stdout -----
3.12
----- stderr -----
warning: No interpreter found for Python 3.12 in system path
"###);

Ok(())
}

Expand Down

0 comments on commit f8cfdcf

Please sign in to comment.