mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Respect .python-version
files and fetch manged toolchains in uv project commands (#4361)
As in #4360, updates the uv project CLI to respect `.python-version` files as default Python version requests. Additionally, updates project interpreter discovery to fetch managed toolchains as in `uv venv --preview`.
This commit is contained in:
parent
903dfc2f1f
commit
1ce21475a5
11 changed files with 120 additions and 51 deletions
|
@ -32,7 +32,7 @@ pub struct BaseClientBuilder<'a> {
|
|||
keyring: KeyringProviderType,
|
||||
native_tls: bool,
|
||||
retries: u32,
|
||||
connectivity: Connectivity,
|
||||
pub connectivity: Connectivity,
|
||||
client: Option<Client>,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
platform: Option<&'a Platform>,
|
||||
|
|
|
@ -929,6 +929,16 @@ pub enum Connectivity {
|
|||
Offline,
|
||||
}
|
||||
|
||||
impl Connectivity {
|
||||
pub fn is_online(&self) -> bool {
|
||||
matches!(self, Self::Online)
|
||||
}
|
||||
|
||||
pub fn is_offline(&self) -> bool {
|
||||
matches!(self, Self::Offline)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
|
|
@ -144,7 +144,11 @@ impl Toolchain {
|
|||
// Perform a find first
|
||||
match Self::find(python.clone(), system, preview, cache) {
|
||||
Ok(venv) => Ok(venv),
|
||||
Err(Error::NotFound(_)) if system.is_allowed() && preview.is_enabled() => {
|
||||
Err(Error::NotFound(_))
|
||||
if system.is_allowed()
|
||||
&& preview.is_enabled()
|
||||
&& client_builder.connectivity.is_online() =>
|
||||
{
|
||||
debug!("Requested Python not found, checking for available download...");
|
||||
let request = if let Some(request) = python {
|
||||
request
|
||||
|
|
|
@ -5,6 +5,7 @@ use uv_distribution::pyproject_mut::PyProjectTomlMut;
|
|||
use uv_git::GitResolver;
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
|
||||
use uv_toolchain::ToolchainRequest;
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
|
||||
use uv_cache::Cache;
|
||||
|
@ -41,7 +42,15 @@ pub(crate) async fn add(
|
|||
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(ToolchainRequest::parse),
|
||||
connectivity,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
|
|
|
@ -18,7 +18,7 @@ use uv_resolver::{
|
|||
ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, PreReleaseMode, RequiresPython,
|
||||
ResolutionMode,
|
||||
};
|
||||
use uv_toolchain::Interpreter;
|
||||
use uv_toolchain::{Interpreter, ToolchainRequest};
|
||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -47,7 +47,15 @@ pub(crate) async fn lock(
|
|||
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
||||
|
||||
// Find an interpreter for the project
|
||||
let interpreter = project::find_interpreter(&workspace, python.as_deref(), cache, printer)?;
|
||||
let interpreter = project::find_interpreter(
|
||||
&workspace,
|
||||
python.as_deref().map(ToolchainRequest::parse),
|
||||
connectivity,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Perform the lock operation.
|
||||
match do_lock(
|
||||
|
|
|
@ -18,7 +18,8 @@ use uv_installer::{SatisfiesResult, SitePackages};
|
|||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder, RequiresPython};
|
||||
use uv_toolchain::{
|
||||
Interpreter, PythonEnvironment, SystemPython, Toolchain, ToolchainRequest, VersionRequest,
|
||||
request_from_version_file, Interpreter, PythonEnvironment, SystemPython, Toolchain,
|
||||
ToolchainRequest, VersionRequest,
|
||||
};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
@ -99,7 +100,7 @@ pub(crate) fn find_environment(
|
|||
/// Check if the given interpreter satisfies the project's requirements.
|
||||
pub(crate) fn interpreter_meets_requirements(
|
||||
interpreter: &Interpreter,
|
||||
requested_python: Option<&str>,
|
||||
requested_python: Option<&ToolchainRequest>,
|
||||
requires_python: Option<&RequiresPython>,
|
||||
cache: &Cache,
|
||||
) -> bool {
|
||||
|
@ -108,8 +109,7 @@ pub(crate) fn interpreter_meets_requirements(
|
|||
// we'll fail at the build or at last the install step when we aren't able to install
|
||||
// the editable wheel for the current project into the venv.
|
||||
// TODO(konsti): Do we want to support a workspace python version requirement?
|
||||
if let Some(python) = requested_python {
|
||||
let request = ToolchainRequest::parse(python);
|
||||
if let Some(request) = requested_python {
|
||||
if request.satisfied(interpreter, cache) {
|
||||
debug!("Interpreter meets the requested python {}", request);
|
||||
return true;
|
||||
|
@ -136,20 +136,36 @@ pub(crate) fn interpreter_meets_requirements(
|
|||
}
|
||||
|
||||
/// Find the interpreter to use in the current project.
|
||||
pub(crate) fn find_interpreter(
|
||||
pub(crate) async fn find_interpreter(
|
||||
workspace: &Workspace,
|
||||
python: Option<&str>,
|
||||
python_request: Option<ToolchainRequest>,
|
||||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<Interpreter, ProjectError> {
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
|
||||
// (1) Explicit request from user
|
||||
let python_request = if let Some(request) = python_request {
|
||||
Some(request)
|
||||
// (2) Request from `.python-version`
|
||||
} else if let Some(request) = request_from_version_file().await? {
|
||||
Some(request)
|
||||
// (3) `Requires-Python` in `pyproject.toml`
|
||||
} else {
|
||||
requires_python
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone())))
|
||||
};
|
||||
|
||||
// Read from the virtual environment first
|
||||
match find_environment(workspace, cache) {
|
||||
Ok(venv) => {
|
||||
if interpreter_meets_requirements(
|
||||
venv.interpreter(),
|
||||
python,
|
||||
python_request.as_ref(),
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
|
@ -160,34 +176,20 @@ pub(crate) fn find_interpreter(
|
|||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
// Otherwise, find a system interpreter to use
|
||||
let interpreter = if let Some(request) = python.map(ToolchainRequest::parse).or(requires_python
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone()))))
|
||||
{
|
||||
Toolchain::find_requested(
|
||||
&request,
|
||||
SystemPython::Required,
|
||||
PreviewMode::Enabled,
|
||||
cache,
|
||||
)
|
||||
} else {
|
||||
Toolchain::find(None, SystemPython::Required, PreviewMode::Enabled, cache)
|
||||
}?
|
||||
.into_interpreter();
|
||||
let client_builder = BaseClientBuilder::default()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
|
||||
if let Some(requires_python) = requires_python.as_ref() {
|
||||
if !requires_python.contains(interpreter.python_version()) {
|
||||
warn_user!(
|
||||
"The Python {} you requested with {} is incompatible with the requirement of the \
|
||||
project of {}",
|
||||
interpreter.python_version(),
|
||||
python.unwrap_or("(default)"),
|
||||
requires_python
|
||||
);
|
||||
}
|
||||
}
|
||||
// Locate the Python interpreter to use in the environment
|
||||
let interpreter = Toolchain::find_or_fetch(
|
||||
python_request,
|
||||
SystemPython::Required,
|
||||
PreviewMode::Enabled,
|
||||
client_builder,
|
||||
cache,
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
|
@ -196,13 +198,25 @@ pub(crate) fn find_interpreter(
|
|||
interpreter.sys_executable().user_display().cyan()
|
||||
)?;
|
||||
|
||||
if let Some(requires_python) = requires_python.as_ref() {
|
||||
if !requires_python.contains(interpreter.python_version()) {
|
||||
warn_user!(
|
||||
"The Python interpreter ({}) is incompatible with the project Python requirement {}",
|
||||
interpreter.python_version(),
|
||||
requires_python
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(interpreter)
|
||||
}
|
||||
|
||||
/// Initialize a virtual environment for the current project.
|
||||
pub(crate) fn init_environment(
|
||||
pub(crate) async fn init_environment(
|
||||
workspace: &Workspace,
|
||||
python: Option<&str>,
|
||||
python: Option<ToolchainRequest>,
|
||||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<PythonEnvironment, ProjectError> {
|
||||
|
@ -213,7 +227,7 @@ pub(crate) fn init_environment(
|
|||
Ok(venv) => {
|
||||
if interpreter_meets_requirements(
|
||||
venv.interpreter(),
|
||||
python,
|
||||
python.as_ref(),
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
|
@ -234,7 +248,8 @@ pub(crate) fn init_environment(
|
|||
};
|
||||
|
||||
// Find an interpreter to create the environment with
|
||||
let interpreter = find_interpreter(workspace, python, cache, printer)?;
|
||||
let interpreter =
|
||||
find_interpreter(workspace, python, connectivity, native_tls, cache, printer).await?;
|
||||
|
||||
let venv = workspace.venv();
|
||||
writeln!(
|
||||
|
|
|
@ -6,6 +6,7 @@ use uv_client::Connectivity;
|
|||
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
|
||||
use uv_distribution::pyproject_mut::PyProjectTomlMut;
|
||||
use uv_distribution::ProjectWorkspace;
|
||||
use uv_toolchain::ToolchainRequest;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
|
@ -81,7 +82,15 @@ pub(crate) async fn remove(
|
|||
)?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(ToolchainRequest::parse),
|
||||
connectivity,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Use the default settings.
|
||||
let settings = ResolverSettings::default();
|
||||
|
|
|
@ -60,8 +60,15 @@ pub(crate) async fn run(
|
|||
} else {
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
||||
};
|
||||
let venv =
|
||||
project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(ToolchainRequest::parse),
|
||||
connectivity,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Lock and sync the environment.
|
||||
let lock = project::lock::do_lock(
|
||||
|
|
|
@ -16,7 +16,7 @@ use uv_git::GitResolver;
|
|||
use uv_installer::SitePackages;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Lock};
|
||||
use uv_toolchain::PythonEnvironment;
|
||||
use uv_toolchain::{PythonEnvironment, ToolchainRequest};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -49,7 +49,15 @@ pub(crate) async fn sync(
|
|||
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(ToolchainRequest::parse),
|
||||
connectivity,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Read the lockfile.
|
||||
let lock: Lock = {
|
||||
|
|
|
@ -302,9 +302,7 @@ impl TestContext {
|
|||
.arg(self.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||
.env("UV_TOOLCHAIN_DIR", "")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.env("UV_NO_WRAP", "1")
|
||||
.env("UV_TOOLCHAIN_DIR", "")
|
||||
.env("UV_TEST_PYTHON_PATH", &self.python_path())
|
||||
.current_dir(&self.temp_dir);
|
||||
|
||||
|
|
|
@ -1887,7 +1887,8 @@ fn lock_requires_python() -> Result<()> {
|
|||
.collect();
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(filters, context38.sync(), @r###"
|
||||
// Note we need `--offline` otherwise we'll just fetch a 3.12 interpreter!
|
||||
uv_snapshot!(filters, context38.sync().arg("--offline"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue