mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-26 18:06:45 +00:00
Add hint when Python downloads are disabled (#14522)
Follow-up to https://github.com/astral-sh/uv/pull/14509 to provide the _reason_ downloads are disabled and surface it as a hint rather than a debug log. e.g., ``` ❯ cargo run -q -- run --no-managed-python -p 3.13.4 python error: No interpreter found for Python 3.13.4 in virtual environments or search path hint: A managed Python download is available for Python 3.13.4, but the Python preference is set to 'only system' ```
This commit is contained in:
parent
1dff18897a
commit
02345a5a7d
9 changed files with 187 additions and 59 deletions
|
|
@ -107,18 +107,9 @@ impl PythonInstallation {
|
||||||
Err(err) => err,
|
Err(err) => err,
|
||||||
};
|
};
|
||||||
|
|
||||||
let downloads_enabled = preference.allows_managed()
|
|
||||||
&& python_downloads.is_automatic()
|
|
||||||
&& client_builder.connectivity.is_online();
|
|
||||||
|
|
||||||
if !downloads_enabled {
|
|
||||||
debug!("Python downloads are disabled. Skipping check for available downloads...");
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
match err {
|
match err {
|
||||||
// If Python is missing, we should attempt a download
|
// If Python is missing, we should attempt a download
|
||||||
Error::MissingPython(_) => {}
|
Error::MissingPython(..) => {}
|
||||||
// If we raised a non-critical error, we should attempt a download
|
// If we raised a non-critical error, we should attempt a download
|
||||||
Error::Discovery(ref err) if !err.is_critical() => {}
|
Error::Discovery(ref err) if !err.is_critical() => {}
|
||||||
// Otherwise, this is fatal
|
// Otherwise, this is fatal
|
||||||
|
|
@ -126,40 +117,109 @@ impl PythonInstallation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can't convert the request to a download, throw the original error
|
// If we can't convert the request to a download, throw the original error
|
||||||
let Some(request) = PythonDownloadRequest::from_request(request) else {
|
let Some(download_request) = PythonDownloadRequest::from_request(request) else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Requested Python not found, checking for available download...");
|
let downloads_enabled = preference.allows_managed()
|
||||||
match Self::fetch(
|
&& python_downloads.is_automatic()
|
||||||
request.fill()?,
|
&& client_builder.connectivity.is_online();
|
||||||
|
|
||||||
|
let download = download_request.clone().fill().map(|request| {
|
||||||
|
ManagedPythonDownload::from_request(&request, python_downloads_json_url)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regardless of whether downloads are enabled, we want to determine if the download is
|
||||||
|
// available to power error messages. However, if downloads aren't enabled, we don't want to
|
||||||
|
// report any errors related to them.
|
||||||
|
let download = match download {
|
||||||
|
Ok(Ok(download)) => Some(download),
|
||||||
|
// If the download cannot be found, return the _original_ discovery error
|
||||||
|
Ok(Err(downloads::Error::NoDownloadFound(_))) => {
|
||||||
|
if downloads_enabled {
|
||||||
|
debug!("No downloads are available for {request}");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(err) | Ok(Err(err)) => {
|
||||||
|
if downloads_enabled {
|
||||||
|
// We failed to determine the platform information
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(download) = download else {
|
||||||
|
// N.B. We should only be in this case when downloads are disabled; when downloads are
|
||||||
|
// enabled, we should fail eagerly when something goes wrong with the download.
|
||||||
|
debug_assert!(!downloads_enabled);
|
||||||
|
return Err(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the download is available, but not usable, we attach a hint to the original error.
|
||||||
|
if !downloads_enabled {
|
||||||
|
let for_request = match request {
|
||||||
|
PythonRequest::Default | PythonRequest::Any => String::new(),
|
||||||
|
_ => format!(" for {request}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
match python_downloads {
|
||||||
|
PythonDownloads::Automatic => {}
|
||||||
|
PythonDownloads::Manual => {
|
||||||
|
return Err(err.with_missing_python_hint(format!(
|
||||||
|
"A managed Python download is available{for_request}, but Python downloads are set to 'manual', use `uv python install {}` to install the required version",
|
||||||
|
request.to_canonical_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
PythonDownloads::Never => {
|
||||||
|
return Err(err.with_missing_python_hint(format!(
|
||||||
|
"A managed Python download is available{for_request}, but Python downloads are set to 'never'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match preference {
|
||||||
|
PythonPreference::OnlySystem => {
|
||||||
|
return Err(err.with_missing_python_hint(format!(
|
||||||
|
"A managed Python download is available{for_request}, but the Python preference is set to 'only system'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
PythonPreference::Managed
|
||||||
|
| PythonPreference::OnlyManaged
|
||||||
|
| PythonPreference::System => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !client_builder.connectivity.is_online() {
|
||||||
|
return Err(err.with_missing_python_hint(format!(
|
||||||
|
"A managed Python download is available{for_request}, but uv is set to offline mode"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::fetch(
|
||||||
|
download,
|
||||||
client_builder,
|
client_builder,
|
||||||
cache,
|
cache,
|
||||||
reporter,
|
reporter,
|
||||||
python_install_mirror,
|
python_install_mirror,
|
||||||
pypy_install_mirror,
|
pypy_install_mirror,
|
||||||
python_downloads_json_url,
|
|
||||||
preview,
|
preview,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
|
||||||
Ok(installation) => Ok(installation),
|
|
||||||
// Throw the original error if we couldn't find a download
|
|
||||||
Err(Error::Download(downloads::Error::NoDownloadFound(_))) => Err(err),
|
|
||||||
// But if the download failed, throw that error
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download and install the requested installation.
|
/// Download and install the requested installation.
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
request: PythonDownloadRequest,
|
download: &'static ManagedPythonDownload,
|
||||||
client_builder: &BaseClientBuilder<'_>,
|
client_builder: &BaseClientBuilder<'_>,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
reporter: Option<&dyn Reporter>,
|
reporter: Option<&dyn Reporter>,
|
||||||
python_install_mirror: Option<&str>,
|
python_install_mirror: Option<&str>,
|
||||||
pypy_install_mirror: Option<&str>,
|
pypy_install_mirror: Option<&str>,
|
||||||
python_downloads_json_url: Option<&str>,
|
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let installations = ManagedPythonInstallations::from_settings(None)?.init()?;
|
let installations = ManagedPythonInstallations::from_settings(None)?.init()?;
|
||||||
|
|
@ -167,7 +227,6 @@ impl PythonInstallation {
|
||||||
let scratch_dir = installations.scratch();
|
let scratch_dir = installations.scratch();
|
||||||
let _lock = installations.lock().await?;
|
let _lock = installations.lock().await?;
|
||||||
|
|
||||||
let download = ManagedPythonDownload::from_request(&request, python_downloads_json_url)?;
|
|
||||||
let client = client_builder.build();
|
let client = client_builder.build();
|
||||||
|
|
||||||
info!("Fetching requested Python...");
|
info!("Fetching requested Python...");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! Find requested Python interpreters and query interpreters for information.
|
//! Find requested Python interpreters and query interpreters for information.
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -93,8 +94,8 @@ pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
KeyError(#[from] installation::PythonInstallationKeyError),
|
KeyError(#[from] installation::PythonInstallationKeyError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error("{}{}", .0, if let Some(hint) = .1 { format!("\n\n{}{} {hint}", "hint".bold().cyan(), ":".bold()) } else { String::new() })]
|
||||||
MissingPython(#[from] PythonNotFound),
|
MissingPython(PythonNotFound, Option<String>),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
MissingEnvironment(#[from] environment::EnvironmentNotFound),
|
MissingEnvironment(#[from] environment::EnvironmentNotFound),
|
||||||
|
|
@ -103,6 +104,21 @@ pub enum Error {
|
||||||
InvalidEnvironment(#[from] environment::InvalidEnvironment),
|
InvalidEnvironment(#[from] environment::InvalidEnvironment),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub(crate) fn with_missing_python_hint(self, hint: String) -> Self {
|
||||||
|
match self {
|
||||||
|
Error::MissingPython(err, _) => Error::MissingPython(err, Some(hint)),
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PythonNotFound> for Error {
|
||||||
|
fn from(err: PythonNotFound) -> Self {
|
||||||
|
Error::MissingPython(err, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The mock interpreters are not valid on Windows so we don't have unit test coverage there
|
// The mock interpreters are not valid on Windows so we don't have unit test coverage there
|
||||||
// TODO(zanieb): We should write a mock interpreter script that works on Windows
|
// TODO(zanieb): We should write a mock interpreter script that works on Windows
|
||||||
#[cfg(all(test, unix))]
|
#[cfg(all(test, unix))]
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,8 @@ pub(crate) async fn pin(
|
||||||
{
|
{
|
||||||
Ok(python) => Some(python),
|
Ok(python) => Some(python),
|
||||||
// If no matching Python version is found, don't fail unless `resolved` was requested
|
// If no matching Python version is found, don't fail unless `resolved` was requested
|
||||||
Err(uv_python::Error::MissingPython(err)) if !resolved => {
|
Err(uv_python::Error::MissingPython(err, ..)) if !resolved => {
|
||||||
|
// N.B. We omit the hint and just show the inner error message
|
||||||
warn_user_once!("{err}");
|
warn_user_once!("{err}");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,12 @@ impl TestContext {
|
||||||
"managed installations, search path, or registry".to_string(),
|
"managed installations, search path, or registry".to_string(),
|
||||||
"[PYTHON SOURCES]".to_string(),
|
"[PYTHON SOURCES]".to_string(),
|
||||||
));
|
));
|
||||||
|
self.filters.push((
|
||||||
|
"registry or search path".to_string(),
|
||||||
|
"[PYTHON SOURCES]".to_string(),
|
||||||
|
));
|
||||||
|
self.filters
|
||||||
|
.push(("search path".to_string(), "[PYTHON SOURCES]".to_string()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4318,14 +4318,16 @@ fn lock_requires_python() -> Result<()> {
|
||||||
|
|
||||||
// Install from the lockfile.
|
// Install from the lockfile.
|
||||||
// Note we need to disable Python fetches or we'll just download 3.12
|
// Note we need to disable Python fetches or we'll just download 3.12
|
||||||
uv_snapshot!(context_unsupported.filters(), context_unsupported.sync().arg("--frozen").arg("--no-python-downloads"), @r###"
|
uv_snapshot!(context_unsupported.filters(), context_unsupported.sync().arg("--frozen").arg("--no-python-downloads"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No interpreter found for Python >=3.12 in [PYTHON SOURCES]
|
error: No interpreter found for Python >=3.12 in [PYTHON SOURCES]
|
||||||
"###);
|
|
||||||
|
hint: A managed Python download is available for Python >=3.12, but Python downloads are set to 'never'
|
||||||
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -873,6 +873,8 @@ fn python_find_script_python_not_found() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
No interpreter found in [PYTHON SOURCES]
|
No interpreter found in [PYTHON SOURCES]
|
||||||
|
|
||||||
|
hint: A managed Python download is available, but Python downloads are set to 'never'
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,14 +196,16 @@ fn python_install_automatic() {
|
||||||
uv_snapshot!(context.filters(), context.run()
|
uv_snapshot!(context.filters(), context.run()
|
||||||
.env_remove("VIRTUAL_ENV")
|
.env_remove("VIRTUAL_ENV")
|
||||||
.arg("--no-python-downloads")
|
.arg("--no-python-downloads")
|
||||||
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###"
|
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No interpreter found in [PYTHON SOURCES]
|
error: No interpreter found in [PYTHON SOURCES]
|
||||||
"###);
|
|
||||||
|
hint: A managed Python download is available, but Python downloads are set to 'never'
|
||||||
|
");
|
||||||
|
|
||||||
// Otherwise, we should fetch the latest Python version
|
// Otherwise, we should fetch the latest Python version
|
||||||
uv_snapshot!(context.filters(), context.run()
|
uv_snapshot!(context.filters(), context.run()
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ fn python_pin() {
|
||||||
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("pypy"), @r###"
|
uv_snapshot!(context.filters(), context.python_pin().arg("pypy"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -172,7 +172,7 @@ fn python_pin() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: No interpreter found for PyPy in managed installations or search path
|
warning: No interpreter found for PyPy in managed installations or search path
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
||||||
assert_snapshot!(python_version, @r###"
|
assert_snapshot!(python_version, @r###"
|
||||||
|
|
@ -361,7 +361,7 @@ fn python_pin_global_creates_parent_dirs() {
|
||||||
fn python_pin_no_python() {
|
fn python_pin_no_python() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[]);
|
let context: TestContext = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r###"
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -369,7 +369,7 @@ fn python_pin_no_python() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: No interpreter found for Python 3.12 in managed installations or search path
|
warning: No interpreter found for Python 3.12 in managed installations or search path
|
||||||
"###);
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -448,7 +448,7 @@ fn python_pin_compatible_with_requires_python() -> Result<()> {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Request a version that is compatible and uses a Python variant
|
// Request a version that is compatible and uses a Python variant
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.13t"), @r###"
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.13t"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
@ -456,7 +456,7 @@ fn python_pin_compatible_with_requires_python() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: No interpreter found for Python 3.13t in [PYTHON SOURCES]
|
warning: No interpreter found for Python 3.13t in [PYTHON SOURCES]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// Request a implementation version that is compatible
|
// Request a implementation version that is compatible
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r###"
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r###"
|
||||||
|
|
@ -587,27 +587,17 @@ fn warning_pinned_python_version_not_installed() -> Result<()> {
|
||||||
/// We do need a Python interpreter for `--resolved` pins
|
/// We do need a Python interpreter for `--resolved` pins
|
||||||
#[test]
|
#[test]
|
||||||
fn python_pin_resolve_no_python() {
|
fn python_pin_resolve_no_python() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[]);
|
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_sources();
|
||||||
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
if cfg!(windows) {
|
----- stderr -----
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###"
|
error: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
hint: A managed Python download is available for Python 3.12, but Python downloads are set to 'never'
|
||||||
error: No interpreter found for Python 3.12 in managed installations, search path, or registry
|
");
|
||||||
"###);
|
|
||||||
} else {
|
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: No interpreter found for Python 3.12 in managed installations or search path
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -741,14 +731,16 @@ fn python_pin_resolve() {
|
||||||
// Request an implementation that is not installed
|
// Request an implementation that is not installed
|
||||||
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("pypy"), @r###"
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("pypy"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No interpreter found for PyPy in managed installations or search path
|
error: No interpreter found for PyPy in managed installations or search path
|
||||||
"###);
|
|
||||||
|
hint: A managed Python download is available for PyPy, but Python downloads are set to 'never'
|
||||||
|
");
|
||||||
|
|
||||||
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
|
||||||
|
|
@ -2049,6 +2049,54 @@ fn tool_run_python_at_version() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_run_hint_version_not_available() {
|
||||||
|
let context = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_python_sources();
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("python@3.12")
|
||||||
|
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
||||||
|
|
||||||
|
hint: A managed Python download is available for Python 3.12, but Python downloads are set to 'never'
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("python@3.12")
|
||||||
|
.env(EnvVars::UV_PYTHON_DOWNLOADS, "auto")
|
||||||
|
.env(EnvVars::UV_OFFLINE, "true"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
||||||
|
|
||||||
|
hint: A managed Python download is available for Python 3.12, but uv is set to offline mode
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("python@3.12")
|
||||||
|
.env(EnvVars::UV_PYTHON_DOWNLOADS, "auto")
|
||||||
|
.env(EnvVars::UV_NO_MANAGED_PYTHON, "true"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
||||||
|
|
||||||
|
hint: A managed Python download is available for Python 3.12, but the Python preference is set to 'only system'
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_run_python_from() {
|
fn tool_run_python_from() {
|
||||||
let context = TestContext::new_with_versions(&["3.12", "3.11"])
|
let context = TestContext::new_with_versions(&["3.12", "3.11"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue