Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case for uv python list downloads #12381

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4557,6 +4557,11 @@ pub enum PythonCommand {
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct PythonListArgs {
/// A Python request to filter by.
///
/// See `uv help python` to view supported request formats.
pub request: Option<String>,

/// List all Python versions, including old patch versions.
///
/// By default, only the latest patch version is shown for each minor version.
Expand Down
23 changes: 16 additions & 7 deletions crates/uv/src/commands/python/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct PrintData {
/// List available Python installations.
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
pub(crate) async fn list(
request: Option<String>,
kinds: PythonListKinds,
all_versions: bool,
all_platforms: bool,
Expand All @@ -63,23 +64,31 @@ pub(crate) async fn list(
cache: &Cache,
printer: Printer,
) -> Result<ExitStatus> {
let request = request.as_deref().map(PythonRequest::parse);
let base_download_request = if python_preference == PythonPreference::OnlySystem {
None
} else {
// If the user request cannot be mapped to a download request, we won't show any downloads
PythonDownloadRequest::from_request(request.as_ref().unwrap_or(&PythonRequest::Any))
};

let mut output = BTreeSet::new();
if python_preference != PythonPreference::OnlySystem {
if let Some(base_download_request) = base_download_request {
let download_request = match kinds {
PythonListKinds::Installed => None,
PythonListKinds::Downloads => Some(if all_platforms {
PythonDownloadRequest::default()
base_download_request
} else {
PythonDownloadRequest::from_env()?
base_download_request.fill()?
}),
PythonListKinds::Default => {
if python_downloads.is_automatic() {
Some(if all_platforms {
PythonDownloadRequest::default()
base_download_request
} else if all_arches {
PythonDownloadRequest::from_env()?.with_any_arch()
base_download_request.fill()?.with_any_arch()
} else {
PythonDownloadRequest::from_env()?
base_download_request.fill()?
})
} else {
// If fetching is not automatic, then don't show downloads as available by default
Expand Down Expand Up @@ -109,7 +118,7 @@ pub(crate) async fn list(
match kinds {
PythonListKinds::Installed | PythonListKinds::Default => {
Some(find_python_installations(
&PythonRequest::Any,
request.as_ref().unwrap_or(&PythonRequest::Any),
EnvironmentPreference::OnlySystem,
python_preference,
cache,
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let cache = cache.init()?;

commands::python_list(
args.request,
args.kinds,
args.all_versions,
args.all_platforms,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ pub(crate) enum PythonListKinds {
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PythonListSettings {
pub(crate) request: Option<String>,
pub(crate) kinds: PythonListKinds,
pub(crate) all_platforms: bool,
pub(crate) all_arches: bool,
Expand All @@ -833,6 +834,7 @@ impl PythonListSettings {
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: PythonListArgs, _filesystem: Option<FilesystemOptions>) -> Self {
let PythonListArgs {
request,
all_versions,
all_platforms,
all_arches,
Expand All @@ -851,6 +853,7 @@ impl PythonListSettings {
};

Self {
request,
kinds,
all_platforms,
all_arches,
Expand Down
88 changes: 81 additions & 7 deletions crates/uv/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ impl TestContext {
self.filters
.push(("python.exe".to_string(), "python".to_string()));
} else {
self.filters
.push((r"python\d".to_string(), "python".to_string()));
self.filters
.push((r"python\d.\d\d".to_string(), "python".to_string()));
self.filters
.push((r"python\d".to_string(), "python".to_string()));
}
self
}
Expand All @@ -224,6 +224,46 @@ impl TestContext {
self
}

/// Add extra standard filtering for Python installation `bin/` directories, which are not
/// present on Windows but are on Unix. See [`TestContext::with_filtered_virtualenv_bin`] for
/// the virtual environment equivalent.
#[must_use]
pub fn with_filtered_python_install_bin(mut self) -> Self {
if cfg!(unix) {
self.filters.push((
r"[\\/]bin/python".to_string(),
"/[INSTALL-BIN]/python".to_string(),
));
} else {
self.filters.push((
r"[\\/]/python".to_string(),
"/[INSTALL-BIN]/python".to_string(),
));
}
self
}

/// Add extra filtering for ` -> <PATH>` symlink display for Python versions in the test
/// context, e.g., for use in `uv python list`.
#[must_use]
pub fn with_filtered_python_symlinks(mut self) -> Self {
for (version, executable) in &self.python_versions {
if fs_err::symlink_metadata(executable).unwrap().is_symlink() {
self.filters.extend(
Self::path_patterns(executable.read_link().unwrap())
.into_iter()
.map(|pattern| (format! {" -> {pattern}"}, String::new())),
);
}
// Drop links that are byproducts of the test context too
self.filters.push((
regex::escape(&format!(" -> [PYTHON-{version}]")),
String::new(),
));
}
self
}

/// Add extra standard filtering for a given path.
#[must_use]
pub fn with_filtered_path(mut self, path: &Path, name: &str) -> Self {
Expand Down Expand Up @@ -256,11 +296,33 @@ impl TestContext {
/// Adds a filter that ignores platform information in a Python installation key.
pub fn with_filtered_python_keys(mut self) -> Self {
// Filter platform keys
self.filters.push((
r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+))?[a-z]?(?:\+[a-z]+)?)-[a-z0-9]+-[a-z0-9_]+-[a-z]+"
.to_string(),
"$1-[PLATFORM]".to_string(),
));
let platform_re = r"(?x)
( # We capture the group before the platform
(?:cpython|pypy) # Python implementation
-
\d+\.\d+ # Major and minor version
(?: # The patch version is handled separately
\.
(?:
\[X\] # A previously filtered patch version [X]
| # OR
\d+ # An actual patch version
)
)? # (we allow the patch version to be missing entirely, e.g., in a request)
([a-z]+[0-9]*)? # Pre-release version component, e.g., `a6` or `rc2`
(?:
\+[a-z]+ # An optional variant variant, such as `+free-threaded
)?
)
-
[a-z0-9]+ # Operating system (e.g., 'macos')
-
[a-z0-9_]+ # Architecture (e.g., 'aarch64')
-
[a-z]+ # Libc (e.g., 'none')
";
self.filters
.push((platform_re.to_string(), "$1-[PLATFORM]".to_string()));
self
}

Expand Down Expand Up @@ -803,6 +865,18 @@ impl TestContext {
command
}

/// Create a `uv python list` command with options shared across scenarios.
pub fn python_list(&self) -> Command {
let mut command = self.new_command();
command
.arg("python")
.arg("list")
.env(EnvVars::UV_PYTHON_INSTALL_DIR, "")
.current_dir(&self.temp_dir);
self.add_shared_options(&mut command, false);
command
}

/// Create a `uv python install` command with options shared across scenarios.
pub fn python_install(&self) -> Command {
let mut command = self.new_command();
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ mod python_dir;
#[cfg(feature = "python")]
mod python_find;

#[cfg(feature = "python")]
mod python_list;

#[cfg(feature = "python-managed")]
mod python_install;

Expand Down
Loading
Loading