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.
This commit is contained in:
Charlie Marsh 2024-02-28 09:48:49 -05:00 committed by GitHub
parent 995fba8fec
commit 23afa09fae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 60 additions and 42 deletions

View file

@ -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}`")]

View file

@ -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<Path>,
platform: Platform,
cache: &Cache,
) -> Result<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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

View file

@ -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<DateTime<Utc>>,
python: Option<PathBuf>,
python: Option<String>,
cache: Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
@ -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)?
};

View file

@ -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<PathBuf>,
python: Option<String>,
cache: Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
@ -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)?
};

View file

@ -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<PathBuf>,
python: Option<String>,
cache: Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
@ -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)?
};

View file

@ -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<PathBuf>,
///
/// 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<String>,
/// 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<PathBuf>,
///
/// 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<String>,
/// 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<PathBuf>,
///
/// 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<String>,
}
#[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<String>,