mirror of https://github.com/astral-sh/uv
Use python from `python -m uv` as default
Python tools run with `python -m <tool>` will use this `python` as their default python, including pip, virtualenv and the built-in venv. Calling Python tools this way is common, the [pip docs](https://pip.pypa.io/en/stable/user_guide/) use `python -m pip` exclusively, the built-in venv can only be called this way and certain project layouts require `python -m pytest` over `pytest`. This python interpreter takes precedence over the currently active (v)env. These tools are all written in python and read `sys.executable`. To emulate this, we're setting `UV_DEFAULT_PYTHON` in the python module-launcher shim and read it in the default python discovery, prioritizing it over the active venv. User can also set `UV_DEFAULT_PYTHON` for their own purposes. The test covers only half of the feature since we don't build the python package before running the tests. Fixes #2058 Fixes #2222
This commit is contained in:
parent
7964bfbb2b
commit
d8845dc444
|
|
@ -6,7 +6,7 @@ use std::path::PathBuf;
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_fs::normalize_path;
|
use uv_fs::{normalize_path, Simplified};
|
||||||
|
|
||||||
use crate::interpreter::InterpreterInfoError;
|
use crate::interpreter::InterpreterInfoError;
|
||||||
use crate::python_environment::{detect_python_executable, detect_virtual_env};
|
use crate::python_environment::{detect_python_executable, detect_virtual_env};
|
||||||
|
|
@ -61,10 +61,16 @@ pub fn find_requested_python(request: &str, cache: &Cache) -> Result<Option<Inte
|
||||||
///
|
///
|
||||||
/// We prefer the test overwrite `UV_TEST_PYTHON_PATH` if it is set, otherwise `python3`/`python` or
|
/// We prefer the test overwrite `UV_TEST_PYTHON_PATH` if it is set, otherwise `python3`/`python` or
|
||||||
/// `python.exe` respectively.
|
/// `python.exe` respectively.
|
||||||
|
///
|
||||||
|
/// When `system` is set, we ignore the `python -m uv` due to the `--system` flag.
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn find_default_python(cache: &Cache) -> Result<Interpreter, Error> {
|
pub fn find_default_python(cache: &Cache, system: bool) -> Result<Interpreter, Error> {
|
||||||
debug!("Starting interpreter discovery for default Python");
|
let selector = if system {
|
||||||
try_find_default_python(cache)?.ok_or(if cfg!(windows) {
|
PythonVersionSelector::System
|
||||||
|
} else {
|
||||||
|
PythonVersionSelector::Default
|
||||||
|
};
|
||||||
|
find_python(selector, cache)?.ok_or(if cfg!(windows) {
|
||||||
Error::NoPythonInstalledWindows
|
Error::NoPythonInstalledWindows
|
||||||
} else if cfg!(unix) {
|
} else if cfg!(unix) {
|
||||||
Error::NoPythonInstalledUnix
|
Error::NoPythonInstalledUnix
|
||||||
|
|
@ -73,11 +79,6 @@ pub fn find_default_python(cache: &Cache) -> Result<Interpreter, Error> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as [`find_default_python`] but returns `None` if no python is found instead of returning an `Err`.
|
|
||||||
pub(crate) fn try_find_default_python(cache: &Cache) -> Result<Option<Interpreter>, Error> {
|
|
||||||
find_python(PythonVersionSelector::Default, cache)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a Python version matching `selector`.
|
/// Find a Python version matching `selector`.
|
||||||
///
|
///
|
||||||
/// It searches for an existing installation in the following order:
|
/// It searches for an existing installation in the following order:
|
||||||
|
|
@ -95,6 +96,20 @@ fn find_python(
|
||||||
selector: PythonVersionSelector,
|
selector: PythonVersionSelector,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
) -> Result<Option<Interpreter>, Error> {
|
) -> Result<Option<Interpreter>, Error> {
|
||||||
|
if selector != PythonVersionSelector::System {
|
||||||
|
// `python -m uv` passes `sys.executable` as `UV_DEFAULT_PYTHON`. Users expect that this Python
|
||||||
|
// version is used as it is the recommended or sometimes even only way to use tools, e.g. pip
|
||||||
|
// (`python3.10 -m pip`) and venv (`python3.10 -m venv`).
|
||||||
|
if let Some(default_python) = env::var_os("UV_DEFAULT_PYTHON") {
|
||||||
|
debug!(
|
||||||
|
"Trying UV_DEFAULT_PYTHON at {}",
|
||||||
|
default_python.simplified_display()
|
||||||
|
);
|
||||||
|
let interpreter = Interpreter::query(default_python, cache)?;
|
||||||
|
return Ok(Some(interpreter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let UV_TEST_PYTHON_PATH = env::var_os("UV_TEST_PYTHON_PATH");
|
let UV_TEST_PYTHON_PATH = env::var_os("UV_TEST_PYTHON_PATH");
|
||||||
|
|
||||||
|
|
@ -299,7 +314,7 @@ impl PythonInstallation {
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
) -> Result<Option<Interpreter>, Error> {
|
) -> Result<Option<Interpreter>, Error> {
|
||||||
let selected = match selector {
|
let selected = match selector {
|
||||||
PythonVersionSelector::Default => true,
|
PythonVersionSelector::Default | PythonVersionSelector::System => true,
|
||||||
|
|
||||||
PythonVersionSelector::Major(major) => self.major() == major,
|
PythonVersionSelector::Major(major) => self.major() == major,
|
||||||
|
|
||||||
|
|
@ -339,9 +354,11 @@ impl PythonInstallation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
enum PythonVersionSelector {
|
enum PythonVersionSelector {
|
||||||
Default,
|
Default,
|
||||||
|
/// Like default, but skip over the `python` from `python -m uv`.
|
||||||
|
System,
|
||||||
Major(u8),
|
Major(u8),
|
||||||
MajorMinor(u8, u8),
|
MajorMinor(u8, u8),
|
||||||
MajorMinorPatch(u8, u8, u8),
|
MajorMinorPatch(u8, u8, u8),
|
||||||
|
|
@ -360,7 +377,7 @@ impl PythonVersionSelector {
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Default => [Some(python3), Some(python), None, None],
|
Self::Default | Self::System => [Some(python3), Some(python), None, None],
|
||||||
Self::Major(major) => [
|
Self::Major(major) => [
|
||||||
Some(Cow::Owned(format!("python{major}{extension}"))),
|
Some(Cow::Owned(format!("python{major}{extension}"))),
|
||||||
Some(python),
|
Some(python),
|
||||||
|
|
@ -386,7 +403,7 @@ impl PythonVersionSelector {
|
||||||
|
|
||||||
fn major(self) -> Option<u8> {
|
fn major(self) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => None,
|
Self::Default | Self::System => None,
|
||||||
Self::Major(major) => Some(major),
|
Self::Major(major) => Some(major),
|
||||||
Self::MajorMinor(major, _) => Some(major),
|
Self::MajorMinor(major, _) => Some(major),
|
||||||
Self::MajorMinorPatch(major, _, _) => Some(major),
|
Self::MajorMinorPatch(major, _, _) => Some(major),
|
||||||
|
|
@ -471,6 +488,21 @@ fn find_version(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `python -m uv` passes `sys.executable` as `UV_DEFAULT_PYTHON`. Users expect that this Python
|
||||||
|
// version is used as it is the recommended or sometimes even only way to use tools, e.g. pip
|
||||||
|
// (`python3.10 -m pip`) and venv (`python3.10 -m venv`). This is duplicated in
|
||||||
|
// `find_requested_python`, but we need to do it here to take precedence over the active venv.
|
||||||
|
if let Some(default_python) = env::var_os("UV_DEFAULT_PYTHON") {
|
||||||
|
debug!(
|
||||||
|
"Trying UV_DEFAULT_PYTHON at {}",
|
||||||
|
default_python.simplified_display()
|
||||||
|
);
|
||||||
|
let interpreter = Interpreter::query(default_python, cache)?;
|
||||||
|
if version_matches(&interpreter) {
|
||||||
|
return Ok(Some(interpreter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the venv Python matches.
|
// Check if the venv Python matches.
|
||||||
if let Some(venv) = detect_virtual_env()? {
|
if let Some(venv) = detect_virtual_env()? {
|
||||||
let executable = detect_python_executable(venv);
|
let executable = detect_python_executable(venv);
|
||||||
|
|
@ -486,7 +518,7 @@ fn find_version(
|
||||||
let interpreter = if let Some(python_version) = python_version {
|
let interpreter = if let Some(python_version) = python_version {
|
||||||
find_requested_python(&python_version.string, cache)?
|
find_requested_python(&python_version.string, cache)?
|
||||||
} else {
|
} else {
|
||||||
try_find_default_python(cache)?
|
find_python(PythonVersionSelector::Default, cache)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(interpreter) = interpreter {
|
if let Some(interpreter) = interpreter {
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ impl PythonEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`PythonEnvironment`] for the default Python interpreter.
|
/// Create a [`PythonEnvironment`] for the default Python interpreter.
|
||||||
pub fn from_default_python(cache: &Cache) -> Result<Self, Error> {
|
pub fn from_default_python(cache: &Cache, system: bool) -> Result<Self, Error> {
|
||||||
let interpreter = find_default_python(cache)?;
|
let interpreter = find_default_python(cache, system)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
root: interpreter.prefix().to_path_buf(),
|
root: interpreter.prefix().to_path_buf(),
|
||||||
interpreter,
|
interpreter,
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,8 @@ async fn resolve(
|
||||||
let flat_index = FlatIndex::default();
|
let flat_index = FlatIndex::default();
|
||||||
let index = InMemoryIndex::default();
|
let index = InMemoryIndex::default();
|
||||||
// TODO(konstin): Should we also use the bootstrapped pythons here?
|
// TODO(konstin): Should we also use the bootstrapped pythons here?
|
||||||
let real_interpreter =
|
let real_interpreter = find_default_python(&Cache::temp().unwrap(), false)
|
||||||
find_default_python(&Cache::temp().unwrap()).expect("Expected a python to be installed");
|
.expect("Expected a python to be installed");
|
||||||
let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone());
|
let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone());
|
||||||
let build_context = DummyContext::new(Cache::temp()?, interpreter.clone());
|
let build_context = DummyContext::new(Cache::temp()?, interpreter.clone());
|
||||||
let resolver = Resolver::new(
|
let resolver = Resolver::new(
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ fn run() -> Result<(), uv_virtualenv::Error> {
|
||||||
uv_interpreter::Error::NoSuchPython(python_request.to_string()),
|
uv_interpreter::Error::NoSuchPython(python_request.to_string()),
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
find_default_python(&cache)?
|
find_default_python(&cache, false)?
|
||||||
};
|
};
|
||||||
create_bare_venv(
|
create_bare_venv(
|
||||||
&location,
|
&location,
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ pub(crate) fn pip_freeze(
|
||||||
let venv = if let Some(python) = python {
|
let venv = if let Some(python) = python {
|
||||||
PythonEnvironment::from_requested_python(python, cache)?
|
PythonEnvironment::from_requested_python(python, cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, true)?
|
||||||
} else {
|
} else {
|
||||||
match PythonEnvironment::from_virtualenv(cache) {
|
match PythonEnvironment::from_virtualenv(cache) {
|
||||||
Ok(venv) => venv,
|
Ok(venv) => venv,
|
||||||
Err(uv_interpreter::Error::VenvNotFound) => {
|
Err(uv_interpreter::Error::VenvNotFound) => {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, false)?
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ pub(crate) async fn pip_install(
|
||||||
let venv = if let Some(python) = python.as_ref() {
|
let venv = if let Some(python) = python.as_ref() {
|
||||||
PythonEnvironment::from_requested_python(python, &cache)?
|
PythonEnvironment::from_requested_python(python, &cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(&cache)?
|
PythonEnvironment::from_default_python(&cache, true)?
|
||||||
} else {
|
} else {
|
||||||
PythonEnvironment::from_virtualenv(&cache)?
|
PythonEnvironment::from_virtualenv(&cache)?
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ pub(crate) fn pip_list(
|
||||||
let venv = if let Some(python) = python {
|
let venv = if let Some(python) = python {
|
||||||
PythonEnvironment::from_requested_python(python, cache)?
|
PythonEnvironment::from_requested_python(python, cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, true)?
|
||||||
} else {
|
} else {
|
||||||
match PythonEnvironment::from_virtualenv(cache) {
|
match PythonEnvironment::from_virtualenv(cache) {
|
||||||
Ok(venv) => venv,
|
Ok(venv) => venv,
|
||||||
Err(uv_interpreter::Error::VenvNotFound) => {
|
Err(uv_interpreter::Error::VenvNotFound) => {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, false)?
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ pub(crate) fn pip_show(
|
||||||
let venv = if let Some(python) = python {
|
let venv = if let Some(python) = python {
|
||||||
PythonEnvironment::from_requested_python(python, cache)?
|
PythonEnvironment::from_requested_python(python, cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, true)?
|
||||||
} else {
|
} else {
|
||||||
match PythonEnvironment::from_virtualenv(cache) {
|
match PythonEnvironment::from_virtualenv(cache) {
|
||||||
Ok(venv) => venv,
|
Ok(venv) => venv,
|
||||||
Err(uv_interpreter::Error::VenvNotFound) => {
|
Err(uv_interpreter::Error::VenvNotFound) => {
|
||||||
PythonEnvironment::from_default_python(cache)?
|
PythonEnvironment::from_default_python(cache, false)?
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ pub(crate) async fn pip_sync(
|
||||||
let venv = if let Some(python) = python.as_ref() {
|
let venv = if let Some(python) = python.as_ref() {
|
||||||
PythonEnvironment::from_requested_python(python, &cache)?
|
PythonEnvironment::from_requested_python(python, &cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(&cache)?
|
PythonEnvironment::from_default_python(&cache, true)?
|
||||||
} else {
|
} else {
|
||||||
PythonEnvironment::from_virtualenv(&cache)?
|
PythonEnvironment::from_virtualenv(&cache)?
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ pub(crate) async fn pip_uninstall(
|
||||||
let venv = if let Some(python) = python.as_ref() {
|
let venv = if let Some(python) = python.as_ref() {
|
||||||
PythonEnvironment::from_requested_python(python, &cache)?
|
PythonEnvironment::from_requested_python(python, &cache)?
|
||||||
} else if system {
|
} else if system {
|
||||||
PythonEnvironment::from_default_python(&cache)?
|
PythonEnvironment::from_default_python(&cache, true)?
|
||||||
} else {
|
} else {
|
||||||
PythonEnvironment::from_virtualenv(&cache)?
|
PythonEnvironment::from_virtualenv(&cache)?
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ async fn venv_impl(
|
||||||
.ok_or(Error::NoSuchPython(python_request.to_string()))
|
.ok_or(Error::NoSuchPython(python_request.to_string()))
|
||||||
.into_diagnostic()?
|
.into_diagnostic()?
|
||||||
} else {
|
} else {
|
||||||
find_default_python(cache).into_diagnostic()?
|
find_default_python(cache, false).into_diagnostic()?
|
||||||
};
|
};
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#![cfg(feature = "python")]
|
#![cfg(feature = "python")]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
@ -731,3 +732,61 @@ fn verify_nested_pyvenv_cfg() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uv_default_python() -> Result<()> {
|
||||||
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
let cache_dir = assert_fs::TempDir::new()?;
|
||||||
|
// The path to a Python 3.12 interpreter
|
||||||
|
let bin312 =
|
||||||
|
create_bin_with_executables(&temp_dir, &["3.12"]).expect("Failed to create bin dir");
|
||||||
|
// The path to a Python 3.10 interpreter
|
||||||
|
let bin310 =
|
||||||
|
create_bin_with_executables(&temp_dir, &["3.10"]).expect("Failed to create bin dir");
|
||||||
|
let python310 = PathBuf::from(bin310).join(if cfg!(unix) {
|
||||||
|
"python3"
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"python.exe"
|
||||||
|
} else {
|
||||||
|
unimplemented!("Only Windows and Unix are supported")
|
||||||
|
});
|
||||||
|
let venv = temp_dir.child(".venv");
|
||||||
|
|
||||||
|
// Create a virtual environment at `.venv`.
|
||||||
|
let filter_venv = regex::escape(&venv.simplified_display().to_string());
|
||||||
|
let filter_prompt = r"Activate with: (?:.*)\\Scripts\\activate";
|
||||||
|
let filters = &[
|
||||||
|
(r"interpreter at: .+", "interpreter at: [PATH]"),
|
||||||
|
(&filter_venv, "/home/ferris/project/.venv"),
|
||||||
|
(
|
||||||
|
filter_prompt,
|
||||||
|
"Activate with: source /home/ferris/project/.venv/bin/activate",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
uv_snapshot!(filters, Command::new(get_bin())
|
||||||
|
.arg("venv")
|
||||||
|
.arg(venv.as_os_str())
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.arg("--exclude-newer")
|
||||||
|
.arg(EXCLUDE_NEWER)
|
||||||
|
// Simulate a PATH the user may have with Python 3.12 being the default.
|
||||||
|
.env("UV_TEST_PYTHON_PATH", bin312.clone())
|
||||||
|
// Simulate `python3.10 -m uv`.
|
||||||
|
.env("UV_DEFAULT_PYTHON", python310)
|
||||||
|
.current_dir(&temp_dir), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Using Python 3.10.13 interpreter at: [PATH]
|
||||||
|
Creating virtualenv at: /home/ferris/project/.venv
|
||||||
|
Activate with: source /home/ferris/project/.venv/bin/activate
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
venv.assert(predicates::path::is_dir());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ def _detect_virtualenv() -> str:
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _run() -> None:
|
def _run() -> None:
|
||||||
uv = os.fsdecode(find_uv_bin())
|
uv = os.fsdecode(find_uv_bin())
|
||||||
|
|
||||||
|
|
@ -30,6 +31,9 @@ def _run() -> None:
|
||||||
if venv:
|
if venv:
|
||||||
env.setdefault("VIRTUAL_ENV", venv)
|
env.setdefault("VIRTUAL_ENV", venv)
|
||||||
|
|
||||||
|
# When running with `python -m uv`, use this `python` as default.
|
||||||
|
env.setdefault("UV_DEFAULT_PYTHON", sys.executable)
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
@ -39,6 +43,5 @@ def _run() -> None:
|
||||||
os.execvpe(uv, [uv, *sys.argv[1:]], env=env)
|
os.execvpe(uv, [uv, *sys.argv[1:]], env=env)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
_run()
|
_run()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue