mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 13:58:29 +00:00
Validate that discovered interpreters meet the Python preference (#7934)
Closes https://github.com/astral-sh/uv/issues/5144 e.g. ``` ❯ cargo run -q -- sync --python-preference only-system Using CPython 3.12.6 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 9ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ❯ cargo run -q -- sync --python-preference only-managed Using CPython 3.12.1 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 11ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ```
This commit is contained in:
parent
2df06ebfbc
commit
b98ac8c224
12 changed files with 544 additions and 11 deletions
|
@ -446,7 +446,16 @@ fn python_executables_from_installed<'a>(
|
|||
.flatten();
|
||||
|
||||
match preference {
|
||||
PythonPreference::OnlyManaged => Box::new(from_managed_installations),
|
||||
PythonPreference::OnlyManaged => {
|
||||
// TODO(zanieb): Ideally, we'd create "fake" managed installation directories for tests,
|
||||
// but for now... we'll just include the test interpreters which are always on the
|
||||
// search path.
|
||||
if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
|
||||
Box::new(from_managed_installations.chain(from_search_path))
|
||||
} else {
|
||||
Box::new(from_managed_installations)
|
||||
}
|
||||
}
|
||||
PythonPreference::Managed => Box::new(
|
||||
from_managed_installations
|
||||
.chain(from_search_path)
|
||||
|
@ -730,6 +739,9 @@ fn python_interpreters<'a>(
|
|||
false
|
||||
}
|
||||
})
|
||||
.filter_ok(move |(source, interpreter)| {
|
||||
satisfies_python_preference(*source, interpreter, preference)
|
||||
})
|
||||
}
|
||||
|
||||
/// Lazily convert Python executables into interpreters.
|
||||
|
@ -857,6 +869,93 @@ fn source_satisfies_environment_preference(
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if a Python interpreter matches the [`PythonPreference`].
|
||||
pub fn satisfies_python_preference(
|
||||
source: PythonSource,
|
||||
interpreter: &Interpreter,
|
||||
preference: PythonPreference,
|
||||
) -> bool {
|
||||
// If the source is "explicit", we will not apply the Python preference, e.g., if the user has
|
||||
// activated a virtual environment, we should always allow it. We may want to invalidate the
|
||||
// environment in some cases, like in projects, but we can't distinguish between explicit
|
||||
// requests for a different Python preference or a persistent preference in a configuration file
|
||||
// which would result in overly aggressive invalidation.
|
||||
let is_explicit = match source {
|
||||
PythonSource::ProvidedPath
|
||||
| PythonSource::ParentInterpreter
|
||||
| PythonSource::ActiveEnvironment
|
||||
| PythonSource::CondaPrefix => true,
|
||||
PythonSource::Managed
|
||||
| PythonSource::DiscoveredEnvironment
|
||||
| PythonSource::SearchPath
|
||||
| PythonSource::SearchPathFirst
|
||||
| PythonSource::Registry
|
||||
| PythonSource::MicrosoftStore
|
||||
| PythonSource::BaseCondaPrefix => false,
|
||||
};
|
||||
|
||||
match preference {
|
||||
PythonPreference::OnlyManaged => {
|
||||
// Perform a fast check using the source before querying the interpreter
|
||||
if matches!(source, PythonSource::Managed) || interpreter.is_managed() {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only managed interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
// If not "only" a kind, any interpreter is okay
|
||||
PythonPreference::Managed | PythonPreference::System => true,
|
||||
PythonPreference::OnlySystem => {
|
||||
let is_system = match source {
|
||||
// A managed interpreter is never a system interpreter
|
||||
PythonSource::Managed => false,
|
||||
// We can't be sure if this is a system interpreter without checking
|
||||
PythonSource::ProvidedPath
|
||||
| PythonSource::ParentInterpreter
|
||||
| PythonSource::ActiveEnvironment
|
||||
| PythonSource::CondaPrefix
|
||||
| PythonSource::DiscoveredEnvironment
|
||||
| PythonSource::SearchPath
|
||||
| PythonSource::SearchPathFirst
|
||||
| PythonSource::Registry
|
||||
| PythonSource::BaseCondaPrefix => !interpreter.is_managed(),
|
||||
// Managed interpreters should never be found in the store
|
||||
PythonSource::MicrosoftStore => true,
|
||||
};
|
||||
|
||||
if is_system {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only system interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an encountered error is critical and should stop discovery.
|
||||
///
|
||||
/// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one.
|
||||
|
@ -2812,6 +2911,18 @@ impl PythonPreference {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the canonical name.
|
||||
// TODO(zanieb): This should be a `Display` impl and we should have a different view for
|
||||
// the sources
|
||||
pub fn canonical_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::OnlyManaged => "only managed",
|
||||
Self::Managed => "prefer managed",
|
||||
Self::System => "prefer system",
|
||||
Self::OnlySystem => "only system",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PythonPreference {
|
||||
|
|
|
@ -158,8 +158,7 @@ impl PythonEnvironment {
|
|||
let installation = match find_python_installation(
|
||||
request,
|
||||
preference,
|
||||
// Ignore managed installations when looking for environments
|
||||
PythonPreference::OnlySystem,
|
||||
PythonPreference::default(),
|
||||
cache,
|
||||
preview,
|
||||
)? {
|
||||
|
|
|
@ -271,15 +271,28 @@ impl Interpreter {
|
|||
///
|
||||
/// Returns `false` if we cannot determine the path of the uv managed Python interpreters.
|
||||
pub fn is_managed(&self) -> bool {
|
||||
if let Ok(test_managed) =
|
||||
std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED)
|
||||
{
|
||||
// During testing, we collect interpreters into an artificial search path and need to
|
||||
// be able to mock whether an interpreter is managed or not.
|
||||
return test_managed.split_ascii_whitespace().any(|item| {
|
||||
let version = <PythonVersion as std::str::FromStr>::from_str(item).expect(
|
||||
"`UV_INTERNAL__TEST_PYTHON_MANAGED` items should be valid Python versions",
|
||||
);
|
||||
if version.patch().is_some() {
|
||||
version.version() == self.python_version()
|
||||
} else {
|
||||
(version.major(), version.minor()) == self.python_tuple()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let Ok(installations) = ManagedPythonInstallations::from_settings(None) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
installations
|
||||
.find_all()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|install| install.path() == self.sys_base_prefix)
|
||||
self.sys_base_prefix.starts_with(installations.root())
|
||||
}
|
||||
|
||||
/// Returns `Some` if the environment is externally managed, optionally including an error
|
||||
|
|
|
@ -8,7 +8,7 @@ use uv_static::EnvVars;
|
|||
pub use crate::discovery::{
|
||||
EnvironmentPreference, Error as DiscoveryError, PythonDownloads, PythonNotFound,
|
||||
PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest,
|
||||
find_python_installations,
|
||||
find_python_installations, satisfies_python_preference,
|
||||
};
|
||||
pub use crate::downloads::PlatformRequest;
|
||||
pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment};
|
||||
|
|
|
@ -376,6 +376,14 @@ impl EnvVars {
|
|||
#[attr_hidden]
|
||||
pub const UV_INTERNAL__SHOW_DERIVATION_TREE: &'static str = "UV_INTERNAL__SHOW_DERIVATION_TREE";
|
||||
|
||||
/// Used to set a temporary directory for some tests.
|
||||
#[attr_hidden]
|
||||
pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR";
|
||||
|
||||
/// Used to force treating an interpreter as "managed" during tests.
|
||||
#[attr_hidden]
|
||||
pub const UV_INTERNAL__TEST_PYTHON_MANAGED: &'static str = "UV_INTERNAL__TEST_PYTHON_MANAGED";
|
||||
|
||||
/// Path to system-level configuration directory on Unix systems.
|
||||
pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS";
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ use uv_pep508::MarkerTreeContents;
|
|||
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, Interpreter, InvalidEnvironmentKind, PythonDownloads, PythonEnvironment,
|
||||
PythonInstallation, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile,
|
||||
VersionFileDiscoveryOptions, VersionRequest,
|
||||
PythonInstallation, PythonPreference, PythonRequest, PythonSource, PythonVariant,
|
||||
PythonVersionFile, VersionFileDiscoveryOptions, VersionRequest, satisfies_python_preference,
|
||||
};
|
||||
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
|
||||
|
@ -664,6 +664,7 @@ impl ScriptInterpreter {
|
|||
&venv,
|
||||
EnvironmentKind::Script,
|
||||
python_request.as_ref(),
|
||||
python_preference,
|
||||
requires_python
|
||||
.as_ref()
|
||||
.map(|(requires_python, _)| requires_python),
|
||||
|
@ -794,6 +795,9 @@ pub(crate) enum EnvironmentIncompatibilityError {
|
|||
"The interpreter in the {0} environment has a different version ({1}) than it was created with ({2})"
|
||||
)]
|
||||
PyenvVersionConflict(EnvironmentKind, Version, Version),
|
||||
|
||||
#[error("The {0} environment's Python interpreter does not meet the Python preference: `{1}`")]
|
||||
PythonPreference(EnvironmentKind, PythonPreference),
|
||||
}
|
||||
|
||||
/// Whether an environment is usable for a project or script, i.e., if it matches the requirements.
|
||||
|
@ -801,6 +805,7 @@ fn environment_is_usable(
|
|||
environment: &PythonEnvironment,
|
||||
kind: EnvironmentKind,
|
||||
python_request: Option<&PythonRequest>,
|
||||
python_preference: PythonPreference,
|
||||
requires_python: Option<&RequiresPython>,
|
||||
cache: &Cache,
|
||||
) -> Result<(), EnvironmentIncompatibilityError> {
|
||||
|
@ -836,6 +841,22 @@ fn environment_is_usable(
|
|||
}
|
||||
}
|
||||
|
||||
if satisfies_python_preference(
|
||||
PythonSource::DiscoveredEnvironment,
|
||||
environment.interpreter(),
|
||||
python_preference,
|
||||
) {
|
||||
trace!(
|
||||
"The virtual environment's Python interpreter meets the Python preference: `{}`",
|
||||
python_preference
|
||||
);
|
||||
} else {
|
||||
return Err(EnvironmentIncompatibilityError::PythonPreference(
|
||||
kind,
|
||||
python_preference,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -889,6 +910,7 @@ impl ProjectInterpreter {
|
|||
&venv,
|
||||
EnvironmentKind::Project,
|
||||
python_request.as_ref(),
|
||||
python_preference,
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
|
|
|
@ -187,6 +187,18 @@ impl TestContext {
|
|||
"virtual environments, managed installations, search path, or registry".to_string(),
|
||||
"[PYTHON SOURCES]".to_string(),
|
||||
));
|
||||
self.filters.push((
|
||||
"virtual environments, search path, or registry".to_string(),
|
||||
"[PYTHON SOURCES]".to_string(),
|
||||
));
|
||||
self.filters.push((
|
||||
"virtual environments, registry, or search path".to_string(),
|
||||
"[PYTHON SOURCES]".to_string(),
|
||||
));
|
||||
self.filters.push((
|
||||
"virtual environments or search path".to_string(),
|
||||
"[PYTHON SOURCES]".to_string(),
|
||||
));
|
||||
self.filters.push((
|
||||
"managed installations or search path".to_string(),
|
||||
"[PYTHON SOURCES]".to_string(),
|
||||
|
@ -415,6 +427,15 @@ impl TestContext {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_versions_as_managed(mut self, versions: &[&str]) -> Self {
|
||||
self.extra_env.push((
|
||||
EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED.into(),
|
||||
versions.iter().join(" ").into(),
|
||||
));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Clear filters on `TestContext`.
|
||||
pub fn clear_filters(mut self) -> Self {
|
||||
self.filters.clear();
|
||||
|
|
|
@ -11684,3 +11684,58 @@ fn strip_shebang_arguments() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_python_preference() {
|
||||
let context =
|
||||
TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]);
|
||||
|
||||
// Create a managed interpreter environment
|
||||
uv_snapshot!(context.filters(), context.venv(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
|
||||
// Install a package, requesting managed Python
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
// Install a package, requesting unmanaged Python
|
||||
// This is allowed, because the virtual environment already exists
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("anyio").arg("--no-managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Audited 1 package in [TIME]
|
||||
");
|
||||
|
||||
// This also works with `VIRTUAL_ENV` unset
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("anyio").arg("--no-managed-python").env_remove("VIRTUAL_ENV"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Audited 1 package in [TIME]
|
||||
");
|
||||
}
|
||||
|
|
|
@ -728,6 +728,57 @@ fn python_find_venv_invalid() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_find_managed() {
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
.with_filtered_python_sources()
|
||||
.with_versions_as_managed(&["3.12"]);
|
||||
|
||||
// We find the managed interpreter
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Request an interpreter that cannot be satisfied
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--managed-python").arg("3.11"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for Python 3.11 in virtual environments or managed installations
|
||||
");
|
||||
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
.with_filtered_python_sources()
|
||||
.with_versions_as_managed(&["3.11"]);
|
||||
|
||||
// We find the unmanaged interpreter
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.12]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Request an interpreter that cannot be satisfied
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--no-managed-python").arg("3.11"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for Python 3.11 in [PYTHON SOURCES]
|
||||
");
|
||||
}
|
||||
|
||||
/// See: <https://github.com/astral-sh/uv/issues/11825>
|
||||
///
|
||||
/// This test will not succeed on macOS if using a Homebrew provided interpreter. The interpreter
|
||||
|
|
|
@ -5500,3 +5500,49 @@ fn run_no_sync_incompatible_python() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_python_preference_no_project() {
|
||||
let context =
|
||||
TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]);
|
||||
|
||||
context.venv().assert().success();
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--managed-python").arg("python").arg("--version"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// `VIRTUAL_ENV` is set here, so we'll ignore the flag
|
||||
uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// If we remove the `VIRTUAL_ENV` variable, we should get the unmanaged Python
|
||||
uv_snapshot!(context.filters(), context.run().arg("--no-managed-python").arg("python").arg("--version").env_remove("VIRTUAL_ENV"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.11.[X]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
|
|
@ -10804,3 +10804,144 @@ fn undeclared_editable() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_python_preference() -> Result<()> {
|
||||
let context = TestContext::new_with_versions(&["3.12", "3.11"]);
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Run an initial sync, with 3.12 as an "unmanaged" interpreter
|
||||
context.sync().assert().success();
|
||||
|
||||
// Mark 3.12 as a managed interpreter for the rest of the tests
|
||||
let context = context.with_versions_as_managed(&["3.12"]);
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// We should invalidate the environment and switch to 3.11
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Removed virtual environment at: .venv
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// We will use the environment if it exists
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// Unless the user requests a Python preference that is incompatible
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Removed virtual environment at: .venv
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// If a interpreter cannot be found, we'll fail
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--managed-python").arg("-p").arg("3.11"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for Python 3.11 in managed installations
|
||||
|
||||
hint: A managed Python download is available for Python 3.11, but Python downloads are set to 'never'
|
||||
");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
python-preference = "only-system"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// We'll respect a `python-preference` in the `pyproject.toml` file
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Removed virtual environment at: .venv
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// But it can be overridden via the CLI
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Removed virtual environment at: .venv
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
// `uv run` will invalidate the environment too
|
||||
uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.11.[X]
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Removed virtual environment at: .venv
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1322,3 +1322,69 @@ fn create_venv_apostrophe() {
|
|||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert_eq!(stdout.trim(), venv_dir.to_string_lossy());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn venv_python_preference() {
|
||||
let context =
|
||||
TestContext::new_with_versions(&["3.12", "3.11"]).with_versions_as_managed(&["3.12"]);
|
||||
|
||||
// Create a managed interpreter environment
|
||||
uv_snapshot!(context.filters(), context.venv(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Creating virtual environment at: .venv
|
||||
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
|
||||
Creating virtual environment at: .venv
|
||||
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.venv(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.venv().arg("--managed-python"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
|
||||
Activate with: source .venv/[BIN]/activate
|
||||
");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue