Cache Python downloads by default

i.e., without requiring opt-in vi `UV_PYTHON_CACHE_DIR`
This commit is contained in:
Zanie Blue 2025-12-10 10:40:15 -06:00
parent caac4814df
commit 6d69bda50b
5 changed files with 84 additions and 90 deletions

View File

@ -21,6 +21,7 @@ use tokio_util::either::Either;
use tracing::{debug, instrument}; use tracing::{debug, instrument};
use url::Url; use url::Url;
use uv_cache::{Cache, CacheBucket};
use uv_client::{BaseClient, WrappedReqwestError, is_transient_network_error}; use uv_client::{BaseClient, WrappedReqwestError, is_transient_network_error};
use uv_distribution_filename::{ExtensionError, SourceDistExtension}; use uv_distribution_filename::{ExtensionError, SourceDistExtension};
use uv_extract::hash::Hasher; use uv_extract::hash::Hasher;
@ -1099,13 +1100,14 @@ impl ManagedPythonDownload {
} }
/// Download and extract a Python distribution, retrying on failure. /// Download and extract a Python distribution, retrying on failure.
#[instrument(skip(client, installation_dir, scratch_dir, reporter), fields(download = % self.key()))] #[instrument(skip(client, installation_dir, scratch_dir, cache, reporter), fields(download = % self.key()))]
pub async fn fetch_with_retry( pub async fn fetch_with_retry(
&self, &self,
client: &BaseClient, client: &BaseClient,
retry_policy: &ExponentialBackoff, retry_policy: &ExponentialBackoff,
installation_dir: &Path, installation_dir: &Path,
scratch_dir: &Path, scratch_dir: &Path,
cache: &Cache,
reinstall: bool, reinstall: bool,
python_install_mirror: Option<&str>, python_install_mirror: Option<&str>,
pypy_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>,
@ -1120,6 +1122,7 @@ impl ManagedPythonDownload {
client, client,
installation_dir, installation_dir,
scratch_dir, scratch_dir,
cache,
reinstall, reinstall,
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,
@ -1167,12 +1170,13 @@ impl ManagedPythonDownload {
} }
/// Download and extract a Python distribution. /// Download and extract a Python distribution.
#[instrument(skip(client, installation_dir, scratch_dir, reporter), fields(download = % self.key()))] #[instrument(skip(client, installation_dir, scratch_dir, cache, reporter), fields(download = % self.key()))]
pub async fn fetch( pub async fn fetch(
&self, &self,
client: &BaseClient, client: &BaseClient,
installation_dir: &Path, installation_dir: &Path,
scratch_dir: &Path, scratch_dir: &Path,
cache: &Cache,
reinstall: bool, reinstall: bool,
python_install_mirror: Option<&str>, python_install_mirror: Option<&str>,
pypy_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>,
@ -1205,10 +1209,13 @@ impl ManagedPythonDownload {
let temp_dir = tempfile::tempdir_in(scratch_dir).map_err(Error::DownloadDirError)?; let temp_dir = tempfile::tempdir_in(scratch_dir).map_err(Error::DownloadDirError)?;
if let Some(python_builds_dir) = // Use the cache directory from the environment variable if set, otherwise use the default
env::var_os(EnvVars::UV_PYTHON_CACHE_DIR).filter(|s| !s.is_empty()) // cache bucket.
{ let python_builds_dir = env::var_os(EnvVars::UV_PYTHON_CACHE_DIR)
let python_builds_dir = PathBuf::from(python_builds_dir); .filter(|s| !s.is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| cache.bucket(CacheBucket::Python));
fs_err::create_dir_all(&python_builds_dir)?; fs_err::create_dir_all(&python_builds_dir)?;
let hash_prefix = match self.sha256.as_deref() { let hash_prefix = match self.sha256.as_deref() {
Some(sha) => { Some(sha) => {
@ -1272,26 +1279,6 @@ impl ManagedPythonDownload {
Direction::Extract, Direction::Extract,
) )
.await?; .await?;
} else {
// Avoid overlong log lines
debug!("Downloading {url}");
debug!(
"Extracting {filename} to temporary location: {}",
temp_dir.path().simplified_display()
);
let (reader, size) = read_url(&url, client).await?;
self.extract_reader(
reader,
temp_dir.path(),
&filename,
ext,
size,
reporter,
Direction::Download,
)
.await?;
}
// Extract the top-level directory. // Extract the top-level directory.
let mut extracted = match uv_extract::strip_component(temp_dir.path()) { let mut extracted = match uv_extract::strip_component(temp_dir.path()) {

View File

@ -261,6 +261,7 @@ impl PythonInstallation {
retry_policy, retry_policy,
installations_dir, installations_dir,
&scratch_dir, &scratch_dir,
cache,
false, false,
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,

View File

@ -14,6 +14,7 @@ use owo_colors::{AnsiColors, OwoColorize};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use tracing::{debug, trace}; use tracing::{debug, trace};
use uv_cache::Cache;
use uv_client::BaseClientBuilder; use uv_client::BaseClientBuilder;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_platform::{Arch, Libc}; use uv_platform::{Arch, Libc};
@ -188,6 +189,7 @@ pub(crate) async fn install(
pypy_install_mirror: Option<String>, pypy_install_mirror: Option<String>,
python_downloads_json_url: Option<String>, python_downloads_json_url: Option<String>,
client_builder: BaseClientBuilder<'_>, client_builder: BaseClientBuilder<'_>,
cache: &Cache,
default: bool, default: bool,
python_downloads: PythonDownloads, python_downloads: PythonDownloads,
no_config: bool, no_config: bool,
@ -470,6 +472,7 @@ pub(crate) async fn install(
&retry_policy, &retry_policy,
installations_dir, installations_dir,
&scratch_dir, &scratch_dir,
cache,
reinstall, reinstall,
python_install_mirror.as_deref(), python_install_mirror.as_deref(),
pypy_install_mirror.as_deref(), pypy_install_mirror.as_deref(),

View File

@ -1601,6 +1601,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = settings::PythonInstallSettings::resolve(args, filesystem, environment); let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
show_settings!(args); show_settings!(args);
let cache = cache.init().await?;
commands::python_install( commands::python_install(
&project_dir, &project_dir,
args.install_dir, args.install_dir,
@ -1614,6 +1615,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.pypy_install_mirror, args.pypy_install_mirror,
args.python_downloads_json_url, args.python_downloads_json_url,
client_builder.subcommand(vec!["python".to_owned(), "install".to_owned()]), client_builder.subcommand(vec!["python".to_owned(), "install".to_owned()]),
&cache,
args.default, args.default,
globals.python_downloads, globals.python_downloads,
cli.top_level.no_config, cli.top_level.no_config,
@ -1630,6 +1632,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
show_settings!(args); show_settings!(args);
let upgrade = commands::PythonUpgrade::Enabled(commands::PythonUpgradeSource::Upgrade); let upgrade = commands::PythonUpgrade::Enabled(commands::PythonUpgradeSource::Upgrade);
let cache = cache.init().await?;
commands::python_install( commands::python_install(
&project_dir, &project_dir,
args.install_dir, args.install_dir,
@ -1643,6 +1646,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.pypy_install_mirror, args.pypy_install_mirror,
args.python_downloads_json_url, args.python_downloads_json_url,
client_builder.subcommand(vec!["python".to_owned(), "upgrade".to_owned()]), client_builder.subcommand(vec!["python".to_owned(), "upgrade".to_owned()]),
&cache,
args.default, args.default,
globals.python_downloads, globals.python_downloads,
cli.top_level.no_config, cli.top_level.no_config,

View File

@ -2712,10 +2712,10 @@ fn python_install_no_cache() {
- cpython-3.14.2-[PLATFORM] (python3.14) - cpython-3.14.2-[PLATFORM] (python3.14)
"); ");
// 3.12 isn't cached, so it can't be installed // 3.12 isn't cached, so it can't be installed offline
let mut filters = context.filters(); let mut filters = context.filters();
filters.push(( filters.push((
"cpython-3.12.*.tar.gz", r"cpython-3\.12\.\d+(%2B|\+)\d+-[a-z0-9_-]+\.tar\.gz",
"cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz", "cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz",
)); ));
filters.push((r"releases/download/\d{8}/", "releases/download/[DATE]/")); filters.push((r"releases/download/\d{8}/", "releases/download/[DATE]/"));
@ -2729,8 +2729,7 @@ fn python_install_no_cache() {
----- stderr ----- ----- stderr -----
error: Failed to install cpython-3.12.12-[PLATFORM] error: Failed to install cpython-3.12.12-[PLATFORM]
Caused by: Failed to download https://github.com/astral-sh/python-build-standalone/releases/download/[DATE]/cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz Caused by: An offline Python installation was requested, but cpython-3.12.12-[PLATFORM] (from https://github.com/astral-sh/python-build-standalone/releases/download/[DATE]/cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz) is missing in [CACHE_DIR]/python-v0
Caused by: Network connectivity is disabled, but the requested data wasn't found in the cache for: `https://github.com/astral-sh/python-build-standalone/releases/download/[DATE]/cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz`
"); ");
} }