Skip to content

Commit

Permalink
Discover and respect .python-version files in parent directories
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Sep 16, 2024
1 parent d3e6765 commit 6f07881
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 17 deletions.
52 changes: 35 additions & 17 deletions crates/uv-python/src/version_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use fs_err as fs;
use itertools::Itertools;
use tracing::debug;
use uv_fs::Simplified;

use crate::PythonRequest;

Expand All @@ -23,37 +24,54 @@ pub struct PythonVersionFile {
}

impl PythonVersionFile {
/// Find a Python version file in the given directory.
/// Find a Python version file in the given directory or any of its parents.
pub async fn discover(
working_directory: impl AsRef<Path>,
// TODO(zanieb): Create a `DiscoverySettings` struct for these options
no_config: bool,
prefer_versions: bool,
) -> Result<Option<Self>, std::io::Error> {
let versions_path = working_directory.as_ref().join(PYTHON_VERSIONS_FILENAME);
let version_path = working_directory.as_ref().join(PYTHON_VERSION_FILENAME);
let Some(path) = Self::find_nearest(working_directory, prefer_versions) else {
return Ok(None);
};

if no_config {
if version_path.exists() {
debug!("Ignoring `.python-version` file due to `--no-config`");
} else if versions_path.exists() {
debug!("Ignoring `.python-versions` file due to `--no-config`");
};
debug!(
"Ignoring Python version file at `{}` due to `--no-config`",
path.user_display()
);
return Ok(None);
}

let paths = if prefer_versions {
[versions_path, version_path]
} else {
[version_path, versions_path]
};
for path in paths {
if let Some(result) = Self::try_from_path(path).await? {
return Ok(Some(result));
// Uses `try_from_path` instead of `from_path` to avoid TOCTOU failures.
Self::try_from_path(path).await
}

fn find_nearest(working_directory: impl AsRef<Path>, prefer_versions: bool) -> Option<PathBuf> {
let mut current = working_directory.as_ref();
loop {
let version_path = current.join(PYTHON_VERSION_FILENAME);
let versions_path = current.join(PYTHON_VERSIONS_FILENAME);

let paths = if prefer_versions {
[versions_path, version_path]
} else {
[version_path, versions_path]
};
for path in paths {
if path.exists() {
return Some(path);
}
}

if let Some(parent) = current.parent() {
current = parent;
} else {
break;
}
}

Ok(None)
None
}

/// Try to read a Python version file at the given path.
Expand Down
32 changes: 32 additions & 0 deletions crates/uv/tests/python_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,38 @@ fn python_find_pin() {
----- stderr -----
"###);

let child_dir = context.temp_dir.child("child");
child_dir.create_dir_all().unwrap();

// We should also find pinned versions in the parent directory
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
"###);

uv_snapshot!(context.filters(), context.python_pin().arg("3.11").current_dir(&child_dir), @r###"
success: true
exit_code: 0
----- stdout -----
Updated `.python-version` from `3.12` -> `3.11`
----- stderr -----
"###);

// Unless the child directory also has a pin
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);
}

#[test]
Expand Down

0 comments on commit 6f07881

Please sign in to comment.