mirror of
https://github.com/j178/prek.git
synced 2026-04-03 17:34:03 +02:00
Try default uv managed python first, fallback to download (#291)
* . * Try default uv managed python first, fallback to download * Fix
This commit is contained in:
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -93,7 +93,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: "Install Python"
|
||||
run: uv python install
|
||||
run: uv python install --no-bin
|
||||
|
||||
- name: "Cargo test"
|
||||
run: |
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: "Install Python"
|
||||
run: uv python install
|
||||
run: uv python install --no-bin
|
||||
|
||||
- name: "Cargo test"
|
||||
run: |
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
cache-local-path: ${{ env.DEV_DRIVE }}/uv-cache
|
||||
|
||||
- name: "Install Python"
|
||||
run: uv python install
|
||||
run: uv python install --no-bin
|
||||
|
||||
- name: "Cargo test"
|
||||
run: |
|
||||
|
||||
@@ -25,7 +25,6 @@ impl EnvVars {
|
||||
// Other environment variables
|
||||
pub const UV_NO_CACHE: &'static str = "UV_NO_CACHE";
|
||||
pub const UV_PYTHON_INSTALL_DIR: &'static str = "UV_PYTHON_INSTALL_DIR";
|
||||
pub const UV_PYTHON_DOWNLOADS: &'static str = "UV_PYTHON_DOWNLOADS";
|
||||
}
|
||||
|
||||
impl EnvVars {
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::store::{Store, ToolBucket};
|
||||
use crate::languages::python::PythonRequest;
|
||||
use crate::languages::version::LanguageRequest;
|
||||
|
||||
use crate::process;
|
||||
use constants::env_vars::EnvVars;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -51,23 +52,9 @@ impl LanguageImpl for Python {
|
||||
};
|
||||
|
||||
// Create venv (auto download Python if needed)
|
||||
let mut cmd = uv.cmd("create venv");
|
||||
cmd.arg("venv")
|
||||
.arg(&info.env_path)
|
||||
.arg("--python-preference")
|
||||
.arg("managed")
|
||||
.arg("--no-project")
|
||||
.arg("--no-config")
|
||||
.env(EnvVars::UV_PYTHON_DOWNLOADS, "true")
|
||||
.env(
|
||||
EnvVars::UV_PYTHON_INSTALL_DIR,
|
||||
store.tools_path(ToolBucket::Python),
|
||||
);
|
||||
if let Some(python) = python_request {
|
||||
cmd.arg("--python").arg(python);
|
||||
}
|
||||
|
||||
cmd.check(true).output().await?;
|
||||
Self::create_venv_with_retry(&uv, store, &info, python_request.as_ref())
|
||||
.await
|
||||
.context("Failed to create Python virtual environment")?;
|
||||
|
||||
// Install dependencies
|
||||
if let Some(repo_path) = hook.repo_path() {
|
||||
@@ -189,6 +176,96 @@ impl LanguageImpl for Python {
|
||||
}
|
||||
}
|
||||
|
||||
impl Python {
|
||||
async fn create_venv_with_retry(
|
||||
uv: &Uv,
|
||||
store: &Store,
|
||||
info: &InstallInfo,
|
||||
python_request: Option<&String>,
|
||||
) -> Result<(), Error> {
|
||||
// Try creating venv without downloads first
|
||||
match Self::create_venv_command(uv, store, info, python_request, false, false)
|
||||
.check(true)
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!("Venv created successfully with no downloads");
|
||||
Ok(())
|
||||
}
|
||||
Err(e @ process::Error::Status { .. }) => {
|
||||
// Check if we can retry with downloads
|
||||
if Self::can_retry_with_downloads(&e) {
|
||||
debug!("Retrying venv creation with managed Python downloads");
|
||||
Self::create_venv_command(uv, store, info, python_request, true, true)
|
||||
.check(true)
|
||||
.output()
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
// If we can't retry, return the original error
|
||||
Err(e.into())
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to create venv: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_venv_command(
|
||||
uv: &Uv,
|
||||
store: &Store,
|
||||
info: &InstallInfo,
|
||||
python_request: Option<&String>,
|
||||
set_install_dir: bool,
|
||||
allow_downloads: bool,
|
||||
) -> Cmd {
|
||||
let mut cmd = uv.cmd("create venv");
|
||||
cmd.arg("venv")
|
||||
.arg(&info.env_path)
|
||||
.arg("--python-preference")
|
||||
.arg("managed")
|
||||
.arg("--no-project")
|
||||
.arg("--no-config");
|
||||
|
||||
if set_install_dir {
|
||||
cmd.env(
|
||||
EnvVars::UV_PYTHON_INSTALL_DIR,
|
||||
store.tools_path(ToolBucket::Python),
|
||||
);
|
||||
}
|
||||
if allow_downloads {
|
||||
cmd.arg("--allow-python-downloads");
|
||||
} else {
|
||||
cmd.arg("--no-python-downloads");
|
||||
}
|
||||
|
||||
if let Some(python) = python_request {
|
||||
cmd.arg("--python").arg(python);
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
fn can_retry_with_downloads(error: &process::Error) -> bool {
|
||||
let process::Error::Status {
|
||||
error:
|
||||
process::StatusError {
|
||||
output: Some(output),
|
||||
..
|
||||
},
|
||||
..
|
||||
} = error
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
stderr.contains("A managed Python download is available")
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_dir(venv: &Path) -> PathBuf {
|
||||
if cfg!(windows) {
|
||||
venv.join("Scripts")
|
||||
|
||||
@@ -4,8 +4,7 @@ use assert_fs::fixture::PathChild;
|
||||
use crate::common::{TestContext, cmd_snapshot};
|
||||
|
||||
/// Test `language_version` parsing.
|
||||
/// Python 3.12.11 and 3.13.5 are installed in the CI environment, when running tests uv can find them
|
||||
/// as system Python.
|
||||
/// Python 3.12.11 and 3.13.5 are installed in the CI environment, when running tests uv can find them.
|
||||
/// Other versions may need to be downloaded while running the tests.
|
||||
#[test]
|
||||
fn language_version() -> anyhow::Result<()> {
|
||||
@@ -144,7 +143,8 @@ fn can_not_download() {
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: command `create venv` exited with an error:
|
||||
error: Failed to create Python virtual environment
|
||||
caused by: command `create venv` exited with an error:
|
||||
|
||||
[status]
|
||||
exit status: 2
|
||||
|
||||
Reference in New Issue
Block a user