Skip to content

Commit

Permalink
Update uv python pin
Browse files Browse the repository at this point in the history
  • Loading branch information
j178 committed Sep 30, 2024
1 parent 4467be3 commit 906bc7c
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 72 deletions.
5 changes: 5 additions & 0 deletions crates/uv-python/src/version_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ impl PythonVersionFile {
self.versions.iter()
}

/// Check if the file contains the given version request.
pub fn contains(&self, request: &PythonRequest) -> bool {
self.versions.contains(request)
}

/// Cast to a list of all versions declared in the file.
pub fn into_versions(self) -> Vec<PythonRequest> {
self.versions
Expand Down
32 changes: 9 additions & 23 deletions crates/uv-workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl};
use pypi_types::{Requirement, RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
use uv_fs::{Simplified, CWD};
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
use uv_python::{PythonVersionFile, PYTHON_VERSION_FILENAME};
use uv_python::PythonVersionFile;
use uv_warnings::{warn_user, warn_user_once};

use crate::pyproject::{
Expand Down Expand Up @@ -84,7 +84,7 @@ pub struct Workspace {
pyproject_toml: PyProjectToml,
/// The `.python-version` or `.python-versions` file of the workspace.
#[cfg_attr(test, serde(skip))]
python_version_file: PythonVersionFile,
python_version_file: Option<PythonVersionFile>,
}

impl Workspace {
Expand Down Expand Up @@ -176,12 +176,8 @@ impl Workspace {
)
};

// Discover the `.python-version` or `.python-versions` file.
let python_version_file =
match PythonVersionFile::discover(&workspace_root, false, false).await? {
Some(file) => file,
None => PythonVersionFile::new(workspace_root.join(PYTHON_VERSION_FILENAME)),
};
PythonVersionFile::discover(&workspace_root, false, false).await?;

debug!(
"Found workspace root: `{}`",
Expand Down Expand Up @@ -555,8 +551,8 @@ impl Workspace {
}

/// The `.python-version` or `.python-versions` file in the workspace.
pub fn python_version_file(&self) -> &PythonVersionFile {
&self.python_version_file
pub fn python_version_file(&self) -> Option<&PythonVersionFile> {
self.python_version_file.as_ref()
}

/// Returns `true` if the path is excluded by the workspace.
Expand Down Expand Up @@ -594,7 +590,7 @@ impl Workspace {
workspace_root: PathBuf,
workspace_definition: ToolUvWorkspace,
workspace_pyproject_toml: PyProjectToml,
workspace_python_version_file: PythonVersionFile,
workspace_python_version_file: Option<PythonVersionFile>,
current_project: Option<WorkspaceMember>,
options: &DiscoveryOptions<'_>,
) -> Result<Workspace, WorkspaceError> {
Expand Down Expand Up @@ -1065,12 +1061,8 @@ impl ProjectWorkspace {
// above it, so the project is an implicit workspace root identical to the project root.
debug!("No workspace root found, using project root");

// Discover the `.python-version` or `.python-versions` file.
let python_version_file =
match PythonVersionFile::discover(&project_path, false, false).await? {
Some(file) => file,
None => PythonVersionFile::new(project_path.join(PYTHON_VERSION_FILENAME)),
};
PythonVersionFile::discover(&project_path, false, false).await?;

let current_project_as_members =
BTreeMap::from_iter([(project.name.clone(), current_project)]);
Expand All @@ -1090,10 +1082,7 @@ impl ProjectWorkspace {
};

let python_version_file =
match PythonVersionFile::discover(&workspace_root, false, false).await? {
Some(file) => file,
None => PythonVersionFile::new(workspace_root.join(PYTHON_VERSION_FILENAME)),
};
PythonVersionFile::discover(&workspace_root, false, false).await?;

debug!(
"Found workspace root: `{}`",
Expand Down Expand Up @@ -1424,10 +1413,7 @@ impl VirtualProject {
.clone();

let python_version_file =
match PythonVersionFile::discover(&project_path, false, false).await? {
Some(file) => file,
None => PythonVersionFile::new(project_path.join(PYTHON_VERSION_FILENAME)),
};
PythonVersionFile::discover(&project_path, false, false).await?;

check_nested_workspaces(&project_path, options);

Expand Down
40 changes: 20 additions & 20 deletions crates/uv/src/commands/project/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context, Result};
use owo_colors::OwoColorize;

use pep440_rs::Version;
use pep508_rs::PackageName;
use tracing::{debug, warn};
Expand Down Expand Up @@ -761,28 +760,29 @@ async fn write_python_version_file(
return Ok(());
};

if let Some(workspace) = workspace {
let version_file = workspace.python_version_file();
// TODO: If the version file already exists, but does not contain the requested version,
// should we update it? (add the version, or replace the existing one)
// For now, we keep the existing behavior: if the file exists, we don't update it.
if !version_file.path().try_exists()? {
version_file
.clone()
.with_versions(vec![python_request.clone()])
.write()
.await?;
// TODO: If the version file already exists, but does not contain the requested version,
// should we update it? (add the version, or replace the existing one)

// Only write the `.python-version` file if it doesn't already exist.
let new_path = if let Some(workspace) = workspace {
if workspace.python_version_file().is_none() {
Some(workspace.install_path().join(PYTHON_VERSION_FILENAME))
} else {
None
}
} else {
if PythonVersionFile::discover(path, false, false)
.await?
.is_none()
{
PythonVersionFile::new(path.join(PYTHON_VERSION_FILENAME))
.with_versions(vec![python_request.clone()])
.write()
.await?;
let existing = PythonVersionFile::discover(path, false, false).await?;
if existing.is_none() {
Some(path.join(PYTHON_VERSION_FILENAME))
} else {
None
}
};
if let Some(path) = new_path {
PythonVersionFile::new(path)
.with_versions(vec![python_request.clone()])
.write()
.await?;
}

Ok(())
Expand Down
51 changes: 36 additions & 15 deletions crates/uv/src/commands/python/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ pub(crate) async fn pin(
}
};

let version_file = PythonVersionFile::discover(project_dir, false, false).await;
let version_file = match &virtual_project {
Some(VirtualProject::Project(project)) => {
project.workspace().python_version_file().cloned()
}
Some(VirtualProject::NonProject(workspace)) => workspace.python_version_file().cloned(),
None => PythonVersionFile::discover(project_dir, false, true).await?,
};

let Some(request) = request else {
// Display the current pinned Python version
if let Some(file) = version_file? {
if let Some(file) = version_file {
for pin in file.versions() {
writeln!(printer.stdout(), "{}", pin.to_canonical_string())?;
if let Some(virtual_project) = &virtual_project {
Expand Down Expand Up @@ -124,33 +130,48 @@ pub(crate) async fn pin(
request
};

let existing = version_file.ok().flatten();
// TODO(zanieb): Allow updating the discovered version file with an `--update` flag.
let new = PythonVersionFile::new(project_dir.join(PYTHON_VERSION_FILENAME))
.with_versions(vec![request]);

new.write().await?;
if let Some(existing) = version_file {
if existing.contains(&request) {
writeln!(
printer.stdout(),
"`{}` is already pinned at `{}`",
existing.path().user_display().cyan(),
request.to_canonical_string().green(),
)?;
return Ok(ExitStatus::Success);
}

if let Some(existing) = existing
.as_ref()
.and_then(PythonVersionFile::version)
.filter(|version| *version != new.version().unwrap())
{
let new = existing.clone().with_versions(vec![request]);
new.write().await?;
writeln!(
printer.stdout(),
"Updated `{}` from `{}` -> `{}`",
new.path().user_display().cyan(),
existing.to_canonical_string().green(),
existing.path().user_display().cyan(),
existing.version().unwrap().to_canonical_string().green(),
new.version().unwrap().to_canonical_string().green()
)?;
} else {
let new_path = match &virtual_project {
Some(VirtualProject::Project(project)) => project
.workspace()
.install_path()
.join(PYTHON_VERSION_FILENAME),
Some(VirtualProject::NonProject(workspace)) => {
workspace.install_path().join(PYTHON_VERSION_FILENAME)
}
None => project_dir.join(PYTHON_VERSION_FILENAME),
};

let new = &PythonVersionFile::new(new_path).with_versions(vec![request]);
new.write().await?;
writeln!(
printer.stdout(),
"Pinned `{}` to `{}`",
new.path().user_display().cyan(),
new.version().unwrap().to_canonical_string().green()
)?;
}
};

Ok(ExitStatus::Success)
}
Expand Down
24 changes: 18 additions & 6 deletions crates/uv/tests/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1908,8 +1908,12 @@ fn init_requires_python_workspace() -> Result<()> {
});

// `.python-version` should be written in the workspace root.
child.child(".python-version").assert(predicate::path::missing());
child.child(".python-versions").assert(predicate::path::missing());
child
.child(".python-version")
.assert(predicate::path::missing());
child
.child(".python-versions")
.assert(predicate::path::missing());

let python_version = fs_err::read_to_string(context.temp_dir.join(".python-version"))?;
insta::with_settings!({
Expand Down Expand Up @@ -1970,8 +1974,12 @@ fn init_requires_python_version() -> Result<()> {
});

// `.python-version` should be written in the workspace root.
child.child(".python-version").assert(predicate::path::missing());
child.child(".python-versions").assert(predicate::path::missing());
child
.child(".python-version")
.assert(predicate::path::missing());
child
.child(".python-versions")
.assert(predicate::path::missing());

let python_version = fs_err::read_to_string(context.temp_dir.join(".python-version"))?;
insta::with_settings!({
Expand Down Expand Up @@ -2033,8 +2041,12 @@ fn init_requires_python_specifiers() -> Result<()> {
});

// `.python-version` should be written in the workspace root.
child.child(".python-version").assert(predicate::path::missing());
child.child(".python-versions").assert(predicate::path::missing());
child
.child(".python-version")
.assert(predicate::path::missing());
child
.child(".python-versions")
.assert(predicate::path::missing());

let python_version = fs_err::read_to_string(context.temp_dir.join(".python-version"))?;
insta::with_settings!({
Expand Down
Loading

0 comments on commit 906bc7c

Please sign in to comment.