Skip to content

Commit

Permalink
respect --project during uv python find (#11990)
Browse files Browse the repository at this point in the history
## Summary

this pr updates a couple discovery-related functions to accept a directory argument, used as the root directory when searching for a virtual environment

## Test Plan

todo
  • Loading branch information
thejchap committed Mar 7, 2025
1 parent 40dce4e commit 0c25a26
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 31 deletions.
110 changes: 84 additions & 26 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::managed::ManagedPythonInstallations;
use crate::microsoft_store::find_microsoft_store_pythons;
use crate::virtualenv::Error as VirtualEnvError;
use crate::virtualenv::{
conda_environment_from_env, virtualenv_from_env, virtualenv_from_working_dir,
conda_environment_from_env, virtualenv_from_dir, virtualenv_from_env,
virtualenv_python_executable, CondaEnvironmentKind,
};
#[cfg(windows)]
Expand Down Expand Up @@ -251,6 +251,7 @@ pub enum Error {
///
/// Notably, "system" environments are excluded. See [`python_executables_from_installed`].
fn python_executables_from_virtual_environments<'a>(
root_directory: &'a Path,
) -> impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a {
let from_active_environment = iter::once_with(|| {
virtualenv_from_env()
Expand All @@ -269,8 +270,8 @@ fn python_executables_from_virtual_environments<'a>(
})
.flatten();

let from_discovered_environment = iter::once_with(|| {
virtualenv_from_working_dir()
let from_discovered_environment = iter::once_with(move || {
virtualenv_from_dir(root_directory)
.map(|path| {
path.map(virtualenv_python_executable)
.map(|path| (PythonSource::DiscoveredEnvironment, path))
Expand Down Expand Up @@ -420,6 +421,7 @@ fn python_executables<'a>(
implementation: Option<&'a ImplementationName>,
environments: EnvironmentPreference,
preference: PythonPreference,
root_directory: &'a Path,
) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
// Always read from `UV_INTERNAL__PARENT_INTERPRETER` — it could be a system interpreter
let from_parent_interpreter = iter::once_with(|| {
Expand All @@ -438,7 +440,7 @@ fn python_executables<'a>(
})
.flatten();

let from_virtual_environments = python_executables_from_virtual_environments();
let from_virtual_environments = python_executables_from_virtual_environments(root_directory);
let from_installed = python_executables_from_installed(version, implementation, preference);

// Limit the search to the relevant environment preference; this avoids unnecessary work like
Expand Down Expand Up @@ -615,16 +617,22 @@ fn python_interpreters<'a>(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &'a Cache,
root_directory: &'a Path,
) -> impl Iterator<Item = Result<(PythonSource, Interpreter), Error>> + 'a {
python_interpreters_from_executables(
// Perform filtering on the discovered executables based on their source. This avoids
// unnecessary interpreter queries, which are generally expensive. We'll filter again
// with `interpreter_satisfies_environment_preference` after querying.
python_executables(version, implementation, environments, preference).filter_ok(
move |(source, path)| {
source_satisfies_environment_preference(*source, path, environments)
},
),
python_executables(
version,
implementation,
environments,
preference,
root_directory,
)
.filter_ok(move |(source, path)| {
source_satisfies_environment_preference(*source, path, environments)
}),
cache,
)
.filter_ok(move |(source, interpreter)| {
Expand Down Expand Up @@ -856,12 +864,25 @@ fn python_interpreters_with_executable_name<'a>(
)
}

/// Iterate over all Python installations that satisfy the given request, starting from the current directory.
///
/// See [`find_python_installations`] for implementation details.
pub fn find_python_installations_at_current_directory<'a>(
request: &'a PythonRequest,
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &'a Cache,
) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
find_python_installations(request, environments, preference, cache)
}

/// Iterate over all Python installations that satisfy the given request.
pub fn find_python_installations<'a>(
request: &'a PythonRequest,
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &'a Cache,
root_directory: &'a Path,
) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
let sources = DiscoveryPreferences {
python_preference: preference,
Expand Down Expand Up @@ -942,8 +963,15 @@ pub fn find_python_installations<'a>(
}
PythonRequest::Any => Box::new({
debug!("Searching for any Python interpreter in {sources}");
python_interpreters(&VersionRequest::Any, None, environments, preference, cache)
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
python_interpreters(
&VersionRequest::Any,
None,
environments,
preference,
cache,
root_directory,
)
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
}),
PythonRequest::Default => Box::new({
debug!("Searching for default Python interpreter in {sources}");
Expand All @@ -953,6 +981,7 @@ pub fn find_python_installations<'a>(
environments,
preference,
cache,
root_directory,
)
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
}),
Expand All @@ -962,8 +991,15 @@ pub fn find_python_installations<'a>(
};
Box::new({
debug!("Searching for {request} in {sources}");
python_interpreters(version, None, environments, preference, cache)
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
python_interpreters(
version,
None,
environments,
preference,
cache,
root_directory,
)
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
})
}
PythonRequest::Implementation(implementation) => Box::new({
Expand All @@ -974,6 +1010,7 @@ pub fn find_python_installations<'a>(
environments,
preference,
cache,
root_directory,
)
.filter_ok(|(_source, interpreter)| {
interpreter
Expand All @@ -994,6 +1031,7 @@ pub fn find_python_installations<'a>(
environments,
preference,
cache,
root_directory,
)
.filter_ok(|(_source, interpreter)| {
interpreter
Expand All @@ -1017,6 +1055,7 @@ pub fn find_python_installations<'a>(
environments,
preference,
cache,
root_directory,
)
.filter_ok(|(_source, interpreter)| request.satisfied_by_interpreter(interpreter))
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
Expand All @@ -1034,8 +1073,10 @@ pub(crate) fn find_python_installation(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &Cache,
root_directory: &Path,
) -> Result<FindPythonResult, Error> {
let installations = find_python_installations(request, environments, preference, cache);
let installations =
find_python_installations(request, environments, preference, cache, root_directory);
let mut first_prerelease = None;
let mut first_error = None;
for result in installations {
Expand Down Expand Up @@ -1136,7 +1177,13 @@ pub fn find_best_python_installation(

// First, check for an exact match (or the first available version if no Python version was provided)
debug!("Looking for exact match for request {request}");
let result = find_python_installation(request, environments, preference, cache);
let result = find_python_installation(
request,
environments,
preference,
cache,
crate::current_dir()?.as_path(),
);
match result {
Ok(Ok(installation)) => {
warn_on_unsupported_python(installation.interpreter());
Expand Down Expand Up @@ -1164,7 +1211,13 @@ pub fn find_best_python_installation(
_ => None,
} {
debug!("Looking for relaxed patch version {request}");
let result = find_python_installation(&request, environments, preference, cache);
let result = find_python_installation(
&request,
environments,
preference,
cache,
crate::current_dir()?.as_path(),
);
match result {
Ok(Ok(installation)) => {
warn_on_unsupported_python(installation.interpreter());
Expand All @@ -1180,16 +1233,21 @@ pub fn find_best_python_installation(
// If a Python version was requested but cannot be fulfilled, just take any version
debug!("Looking for a default Python installation");
let request = PythonRequest::Default;
Ok(
find_python_installation(&request, environments, preference, cache)?.map_err(|err| {
// Use a more general error in this case since we looked for multiple versions
PythonNotFound {
request,
python_preference: err.python_preference,
environment_preference: err.environment_preference,
}
}),
)
Ok(find_python_installation(
&request,
environments,
preference,
cache,
crate::current_dir()?.as_path(),
)?
.map_err(|err| {
// Use a more general error in this case since we looked for multiple versions
PythonNotFound {
request,
python_preference: err.python_preference,
environment_preference: err.environment_preference,
}
}))
}

/// Display a warning if the Python version of the [`Interpreter`] is unsupported by uv.
Expand Down
1 change: 1 addition & 0 deletions crates/uv-python/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ impl PythonEnvironment {
// Ignore managed installations when looking for environments
PythonPreference::OnlySystem,
cache,
crate::current_dir()?.as_path(),
)? {
Ok(installation) => installation,
Err(err) => return Err(EnvironmentNotFound::from(err).into()),
Expand Down
8 changes: 7 additions & 1 deletion crates/uv-python/src/installation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ impl PythonInstallation {
preference: PythonPreference,
cache: &Cache,
) -> Result<Self, Error> {
let installation = find_python_installation(request, environments, preference, cache)??;
let installation = find_python_installation(
request,
environments,
preference,
cache,
crate::current_dir()?.as_path(),
)??;
Ok(installation)
}

Expand Down
8 changes: 4 additions & 4 deletions crates/uv-python/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ pub(crate) fn conda_environment_from_env(kind: CondaEnvironmentKind) -> Option<P

/// Locate a virtual environment by searching the file system.
///
/// Searches for a `.venv` directory in the current or any parent directory. If the current
/// Searches for a `.venv` directory in the provided or any parent directory. If the provided
/// directory is itself a virtual environment (or a subdirectory of a virtual environment), the
/// containing virtual environment is returned.
pub(crate) fn virtualenv_from_working_dir() -> Result<Option<PathBuf>, Error> {
let current_dir = crate::current_dir()?;
pub(crate) fn virtualenv_from_dir(dir: impl AsRef<Path>) -> Result<Option<PathBuf>, Error> {
let dir = dir.as_ref();

for dir in current_dir.ancestors() {
for dir in dir.ancestors() {
// If we're _within_ a virtualenv, return it.
if dir.join("pyvenv.cfg").is_file() {
return Ok(Some(dir.to_path_buf()));
Expand Down

0 comments on commit 0c25a26

Please sign in to comment.