From 23afa09fae7a76f55b7490a69ad61e46888706bf Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 28 Feb 2024 09:48:49 -0500 Subject: [PATCH] Accept (e.g.) `'python3.8'` as `--python` argument (#2031) ## Summary This PR aligns the `uv pip install --python` flag with the `uv venv --python` flag, such that the former now accepts binary names and Python versions by way of using the same `find_requested_python` method under the hood. --- crates/uv-interpreter/src/lib.rs | 4 ++- crates/uv-interpreter/src/virtual_env.rs | 36 +++++++++++----------- crates/uv/src/commands/pip_install.rs | 9 +++--- crates/uv/src/commands/pip_sync.rs | 7 ++--- crates/uv/src/commands/pip_uninstall.rs | 7 ++--- crates/uv/src/main.rs | 39 +++++++++++++++++------- 6 files changed, 60 insertions(+), 42 deletions(-) diff --git a/crates/uv-interpreter/src/lib.rs b/crates/uv-interpreter/src/lib.rs index 73ca26f05..608f3c251 100644 --- a/crates/uv-interpreter/src/lib.rs +++ b/crates/uv-interpreter/src/lib.rs @@ -28,7 +28,9 @@ pub enum Error { #[error("No versions of Python could be found. Is Python installed?")] PythonNotFound, #[error("Failed to locate a virtualenv or Conda environment (checked: `VIRTUAL_ENV`, `CONDA_PREFIX`, and `.venv`). Run `uv venv` to create a virtualenv.")] - NotFound, + VenvNotFound, + #[error("Failed to locate Python interpreter at: `{0}`")] + RequestedPythonNotFound(String), #[error(transparent)] Io(#[from] io::Error), #[error("Failed to query python interpreter `{interpreter}`")] diff --git a/crates/uv-interpreter/src/virtual_env.rs b/crates/uv-interpreter/src/virtual_env.rs index 647c07cbc..dab8b986e 100644 --- a/crates/uv-interpreter/src/virtual_env.rs +++ b/crates/uv-interpreter/src/virtual_env.rs @@ -9,7 +9,7 @@ use uv_fs::{LockedFile, Normalized}; use crate::cfg::PyVenvConfiguration; use crate::python_platform::PythonPlatform; -use crate::{Error, Interpreter}; +use crate::{find_requested_python, Error, Interpreter}; /// A Python executable and its associated platform markers. #[derive(Debug, Clone)] @@ -19,24 +19,11 @@ pub struct Virtualenv { } impl Virtualenv { - /// Create a new virtual environment for a pre-provided Python interpreter. - pub fn from_python( - python: impl AsRef, - platform: Platform, - cache: &Cache, - ) -> Result { - let interpreter = Interpreter::query(python.as_ref(), platform, cache)?; - Ok(Self { - root: interpreter.base_prefix().to_path_buf(), - interpreter, - }) - } - - /// Venv the current Python executable from the host environment. + /// Create a [`Virtualenv`] for an existing virtual environment. pub fn from_env(platform: Platform, cache: &Cache) -> Result { let platform = PythonPlatform::from(platform); let Some(venv) = detect_virtual_env(&platform)? else { - return Err(Error::NotFound); + return Err(Error::VenvNotFound); }; let venv = fs_err::canonicalize(venv)?; let executable = platform.venv_python(&venv); @@ -55,7 +42,7 @@ impl Virtualenv { }) } - /// Creating a new venv from a Python interpreter changes this. + /// Create a [`Virtualenv`] for a new virtual environment, created with the given interpreter. pub fn from_interpreter(interpreter: Interpreter, venv: &Path) -> Self { Self { interpreter: interpreter.with_venv_root(venv.to_path_buf()), @@ -63,6 +50,21 @@ impl Virtualenv { } } + /// Create a [`Virtualenv`] for a Python interpreter specifier (e.g., a path or a binary name). + pub fn from_requested_python( + python: &str, + platform: &Platform, + cache: &Cache, + ) -> Result { + let Some(interpreter) = find_requested_python(python, platform, cache)? else { + return Err(Error::RequestedPythonNotFound(python.to_string())); + }; + Ok(Self { + root: interpreter.base_prefix().to_path_buf(), + interpreter, + }) + } + /// Returns the location of the Python interpreter. pub fn root(&self) -> &Path { &self.root diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index e760c6268..18d0c0dfa 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use std::fmt::Write; - -use std::path::{Path, PathBuf}; +use std::path::Path; use anstream::eprint; use anyhow::{anyhow, Context, Result}; @@ -63,7 +62,7 @@ pub(crate) async fn pip_install( no_binary: &NoBinary, strict: bool, exclude_newer: Option>, - python: Option, + python: Option, cache: Cache, mut printer: Printer, ) -> Result { @@ -106,8 +105,8 @@ pub(crate) async fn pip_install( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = if let Some(python) = python { - Virtualenv::from_python(python, platform, &cache)? + let venv = if let Some(python) = python.as_ref() { + Virtualenv::from_requested_python(python, &platform, &cache)? } else { Virtualenv::from_env(platform, &cache)? }; diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index bf351ecc7..39a7f3e66 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -1,5 +1,4 @@ use std::fmt::Write; -use std::path::PathBuf; use anyhow::{Context, Result}; use itertools::Itertools; @@ -42,7 +41,7 @@ pub(crate) async fn pip_sync( no_build: &NoBuild, no_binary: &NoBinary, strict: bool, - python: Option, + python: Option, cache: Cache, mut printer: Printer, ) -> Result { @@ -74,8 +73,8 @@ pub(crate) async fn pip_sync( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = if let Some(python) = python { - Virtualenv::from_python(python, platform, &cache)? + let venv = if let Some(python) = python.as_ref() { + Virtualenv::from_requested_python(python, &platform, &cache)? } else { Virtualenv::from_env(platform, &cache)? }; diff --git a/crates/uv/src/commands/pip_uninstall.rs b/crates/uv/src/commands/pip_uninstall.rs index 15a982547..09e3439a5 100644 --- a/crates/uv/src/commands/pip_uninstall.rs +++ b/crates/uv/src/commands/pip_uninstall.rs @@ -1,5 +1,4 @@ use std::fmt::Write; -use std::path::PathBuf; use anyhow::Result; use owo_colors::OwoColorize; @@ -18,7 +17,7 @@ use crate::requirements::{RequirementsSource, RequirementsSpecification}; /// Uninstall packages from the current environment. pub(crate) async fn pip_uninstall( sources: &[RequirementsSource], - python: Option, + python: Option, cache: Cache, mut printer: Printer, ) -> Result { @@ -40,8 +39,8 @@ pub(crate) async fn pip_uninstall( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = if let Some(python) = python { - Virtualenv::from_python(python, platform, &cache)? + let venv = if let Some(python) = python.as_ref() { + Virtualenv::from_requested_python(python, &platform, &cache)? } else { Virtualenv::from_env(platform, &cache)? }; diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 747c64610..150c06a78 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -445,8 +445,14 @@ struct PipSyncArgs { /// any parent directory. The `--python` option allows you to specify a different interpreter, /// which is intended for use in continuous integration (CI) environments or other automated /// workflows. - #[clap(long)] - python: Option, + /// + /// Supported formats: + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.) + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. + #[clap(long, short, verbatim_doc_comment)] + python: Option, /// Use legacy `setuptools` behavior when building source distributions without a /// `pyproject.toml`. @@ -623,8 +629,14 @@ struct PipInstallArgs { /// any parent directory. The `--python` option allows you to specify a different interpreter, /// which is intended for use in continuous integration (CI) environments or other automated /// workflows. - #[clap(long)] - python: Option, + /// + /// Supported formats: + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.) + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. + #[clap(long, short, verbatim_doc_comment)] + python: Option, /// Use legacy `setuptools` behavior when building source distributions without a /// `pyproject.toml`. @@ -701,8 +713,14 @@ struct PipUninstallArgs { /// any parent directory. The `--python` option allows you to specify a different interpreter, /// which is intended for use in continuous integration (CI) environments or other automated /// workflows. - #[clap(long)] - python: Option, + /// + /// Supported formats: + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.) + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. + #[clap(long, short, verbatim_doc_comment)] + python: Option, } #[derive(Args)] @@ -741,14 +759,13 @@ struct VenvArgs { /// The Python interpreter to use for the virtual environment. /// /// Supported formats: - /// - `3.10` searches for an installed Python 3.10 (`py --list-paths` on Windows, `python3.10` on Linux/Mac). - /// Specifying a patch version is not supported. - /// - `python3.10` or `python.exe` looks for a binary in `PATH`. - /// - `/home/ferris/.local/bin/python3.10` uses this exact Python. + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.) + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. /// /// Note that this is different from `--python-version` in `pip compile`, which takes `3.10` or `3.10.13` and /// doesn't look for a Python interpreter on disk. - // Short `-p` to match `virtualenv` #[clap(long, short, verbatim_doc_comment)] python: Option,