Skip to content

Commit

Permalink
Respect .python-version files and fetch manged toolchains in uv pro…
Browse files Browse the repository at this point in the history
…ject commands
  • Loading branch information
zanieb committed Jun 18, 2024
1 parent 903dfc2 commit 60af37b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 47 deletions.
11 changes: 10 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand Down
87 changes: 51 additions & 36 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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,
) {
Expand All @@ -160,49 +176,47 @@ 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)
}?
let client_builder = BaseClientBuilder::default()
.connectivity(connectivity)
.native_tls(native_tls);

// 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(),
"Using Python {} interpreter at: {}",
interpreter.python_version(),
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 {} you requested with {} is incompatible with the requirement of the \
project of {}",
"The Python interpreter ({}) is incompatible with the project Python requirement {}",
interpreter.python_version(),
python.unwrap_or("(default)"),
requires_python
);
}
}

writeln!(
printer.stderr(),
"Using Python {} interpreter at: {}",
interpreter.python_version(),
interpreter.sys_executable().user_display().cyan()
)?;

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> {
Expand All @@ -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,
) {
Expand All @@ -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!(
Expand Down
11 changes: 10 additions & 1 deletion crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
11 changes: 9 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 10 additions & 2 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 = {
Expand Down
14 changes: 11 additions & 3 deletions crates/uv/tests/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1888,14 +1888,22 @@ fn lock_requires_python() -> Result<()> {

// Install from the lockfile.
uv_snapshot!(filters, context38.sync(), @r###"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: `uv sync` is experimental and may change without warning.
Removing virtual environment at: .venv
error: No interpreter found for Python >=3.12 in provided path, active virtual environment, or search path
Using Python 3.12.[X] interpreter at: cpython-3.12.[X]-macos-aarch64-none/install/bin/python3
Creating virtualenv at: .venv
Downloaded 5 packages in [TIME]
Installed 5 packages in [TIME]
+ attrs==23.2.0
+ cattrs==23.2.3
+ lsprotocol==2023.0.1
+ project==0.1.0 (from file://[TEMP_DIR]/)
+ pygls==1.3.0
"###);

Ok(())
Expand Down

0 comments on commit 60af37b

Please sign in to comment.