Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via JSON URL (#12974)

## Summary

Part of #12838. Allow users to configure `python-downloads-json-url` in
`uv.toml` and not just from env.

I followed similar PR #8695, so same as there it's also available in the
CLI (I think maybe it's better not to be configurable from the CLI, but
since the mirror parameters are, I think it's better to do the same)


## Test Plan

<!-- How was it tested? -->
This commit is contained in:
Meitar Reihan 2025-04-30 22:52:11 +03:00 committed by GitHub
parent 5ee54b4fa3
commit 0593b967ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 232 additions and 55 deletions

View File

@ -4728,6 +4728,12 @@ pub struct PythonListArgs {
/// Select the output format. /// Select the output format.
#[arg(long, value_enum, default_value_t = PythonListFormat::default())] #[arg(long, value_enum, default_value_t = PythonListFormat::default())]
pub output_format: PythonListFormat, pub output_format: PythonListFormat,
/// URL pointing to JSON of custom Python installations.
///
/// Note that currently, only local paths are supported.
#[arg(long, env = EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL)]
pub python_downloads_json_url: Option<String>,
} }
#[derive(Args)] #[derive(Args)]
@ -4791,6 +4797,12 @@ pub struct PythonInstallArgs {
#[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)] #[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)]
pub pypy_mirror: Option<String>, pub pypy_mirror: Option<String>,
/// URL pointing to JSON of custom Python installations.
///
/// Note that currently, only local paths are supported.
#[arg(long, env = EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL)]
pub python_downloads_json_url: Option<String>,
/// Reinstall the requested Python version, if it's already installed. /// Reinstall the requested Python version, if it's already installed.
/// ///
/// By default, uv will exit successfully if the version is already /// By default, uv will exit successfully if the version is already

View File

@ -268,8 +268,9 @@ impl PythonDownloadRequest {
/// Iterate over all [`PythonDownload`]'s that match this request. /// Iterate over all [`PythonDownload`]'s that match this request.
pub fn iter_downloads( pub fn iter_downloads(
&self, &self,
python_downloads_json_url: Option<&str>,
) -> Result<impl Iterator<Item = &'static ManagedPythonDownload> + use<'_>, Error> { ) -> Result<impl Iterator<Item = &'static ManagedPythonDownload> + use<'_>, Error> {
Ok(ManagedPythonDownload::iter_all()? Ok(ManagedPythonDownload::iter_all(python_downloads_json_url)?
.filter(move |download| self.satisfied_by_download(download))) .filter(move |download| self.satisfied_by_download(download)))
} }
@ -496,8 +497,9 @@ impl ManagedPythonDownload {
/// be searched for — even if a pre-release was not explicitly requested. /// be searched for — even if a pre-release was not explicitly requested.
pub fn from_request( pub fn from_request(
request: &PythonDownloadRequest, request: &PythonDownloadRequest,
python_downloads_json_url: Option<&str>,
) -> Result<&'static ManagedPythonDownload, Error> { ) -> Result<&'static ManagedPythonDownload, Error> {
if let Some(download) = request.iter_downloads()?.next() { if let Some(download) = request.iter_downloads(python_downloads_json_url)?.next() {
return Ok(download); return Ok(download);
} }
@ -505,7 +507,7 @@ impl ManagedPythonDownload {
if let Some(download) = request if let Some(download) = request
.clone() .clone()
.with_prereleases(true) .with_prereleases(true)
.iter_downloads()? .iter_downloads(python_downloads_json_url)?
.next() .next()
{ {
return Ok(download); return Ok(download);
@ -514,32 +516,36 @@ impl ManagedPythonDownload {
Err(Error::NoDownloadFound(request.clone())) Err(Error::NoDownloadFound(request.clone()))
} }
//noinspection RsUnresolvedPath - RustRover can't see through the `include!` //noinspection RsUnresolvedPath - RustRover can't see through the `include!`
/// Iterate over all [`ManagedPythonDownload`]s. /// Iterate over all [`ManagedPythonDownload`]s.
pub fn iter_all() -> Result<impl Iterator<Item = &'static ManagedPythonDownload>, Error> { ///
let runtime_source = std::env::var(EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL); /// Note: The list is generated on the first call to this function.
/// so `python_downloads_json_url` is only used in the first call to this function.
pub fn iter_all(
python_downloads_json_url: Option<&str>,
) -> Result<impl Iterator<Item = &'static ManagedPythonDownload>, Error> {
let downloads = PYTHON_DOWNLOADS.get_or_try_init(|| { let downloads = PYTHON_DOWNLOADS.get_or_try_init(|| {
let json_downloads: HashMap<String, JsonPythonDownload> = let json_downloads: HashMap<String, JsonPythonDownload> = if let Some(json_source) =
if let Ok(json_source) = &runtime_source { python_downloads_json_url
if Url::parse(json_source).is_ok() { {
return Err(Error::RemoteJSONNotSupported()); if Url::parse(json_source).is_ok() {
} return Err(Error::RemoteJSONNotSupported());
}
let file = match fs_err::File::open(json_source) { let file = match fs_err::File::open(json_source) {
Ok(file) => file, Ok(file) => file,
Err(e) => { Err(Error::Io(e)) }?, Err(e) => { Err(Error::Io(e)) }?,
};
serde_json::from_reader(file)
.map_err(|e| Error::InvalidPythonDownloadsJSON(json_source.clone(), e))?
} else {
serde_json::from_str(BUILTIN_PYTHON_DOWNLOADS_JSON).map_err(|e| {
Error::InvalidPythonDownloadsJSON("EMBEDDED IN THE BINARY".to_string(), e)
})?
}; };
serde_json::from_reader(file)
.map_err(|e| Error::InvalidPythonDownloadsJSON(json_source.to_string(), e))?
} else {
serde_json::from_str(BUILTIN_PYTHON_DOWNLOADS_JSON).map_err(|e| {
Error::InvalidPythonDownloadsJSON("EMBEDDED IN THE BINARY".to_string(), e)
})?
};
let result = parse_json_downloads(json_downloads); let result = parse_json_downloads(json_downloads);
Ok(Cow::Owned(result)) Ok(Cow::Owned(result))
})?; })?;

View File

@ -88,6 +88,7 @@ impl PythonInstallation {
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>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let request = request.unwrap_or(&PythonRequest::Default); let request = request.unwrap_or(&PythonRequest::Default);
@ -127,6 +128,7 @@ impl PythonInstallation {
reporter, reporter,
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,
python_downloads_json_url,
) )
.await .await
{ {
@ -146,13 +148,14 @@ impl PythonInstallation {
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>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let installations = ManagedPythonInstallations::from_settings(None)?.init()?; let installations = ManagedPythonInstallations::from_settings(None)?.init()?;
let installations_dir = installations.root(); let installations_dir = installations.root();
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)?; 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...");

View File

@ -819,21 +819,40 @@ pub struct PythonInstallMirrors {
"# "#
)] )]
pub pypy_install_mirror: Option<String>, pub pypy_install_mirror: Option<String>,
/// URL pointing to JSON of custom Python installations.
///
/// Note that currently, only local paths are supported.
#[option(
default = "None",
value_type = "str",
example = r#"
python-downloads-json-url = "/etc/uv/python-downloads.json"
"#
)]
pub python_downloads_json_url: Option<String>,
} }
impl Default for PythonInstallMirrors { impl Default for PythonInstallMirrors {
fn default() -> Self { fn default() -> Self {
PythonInstallMirrors::resolve(None, None) PythonInstallMirrors::resolve(None, None, None)
} }
} }
impl PythonInstallMirrors { impl PythonInstallMirrors {
pub fn resolve(python_mirror: Option<String>, pypy_mirror: Option<String>) -> Self { pub fn resolve(
python_mirror: Option<String>,
pypy_mirror: Option<String>,
python_downloads_json_url: Option<String>,
) -> Self {
let python_mirror_env = std::env::var(EnvVars::UV_PYTHON_INSTALL_MIRROR).ok(); let python_mirror_env = std::env::var(EnvVars::UV_PYTHON_INSTALL_MIRROR).ok();
let pypy_mirror_env = std::env::var(EnvVars::UV_PYPY_INSTALL_MIRROR).ok(); let pypy_mirror_env = std::env::var(EnvVars::UV_PYPY_INSTALL_MIRROR).ok();
let python_downloads_json_url_env =
std::env::var(EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL).ok();
PythonInstallMirrors { PythonInstallMirrors {
python_install_mirror: python_mirror_env.or(python_mirror), python_install_mirror: python_mirror_env.or(python_mirror),
pypy_install_mirror: pypy_mirror_env.or(pypy_mirror), pypy_install_mirror: pypy_mirror_env.or(pypy_mirror),
python_downloads_json_url: python_downloads_json_url_env.or(python_downloads_json_url),
} }
} }
} }
@ -1814,6 +1833,7 @@ pub struct OptionsWire {
// install_mirror: PythonInstallMirrors, // install_mirror: PythonInstallMirrors,
python_install_mirror: Option<String>, python_install_mirror: Option<String>,
pypy_install_mirror: Option<String>, pypy_install_mirror: Option<String>,
python_downloads_json_url: Option<String>,
// #[serde(flatten)] // #[serde(flatten)]
// publish: PublishOptions // publish: PublishOptions
@ -1861,6 +1881,7 @@ impl From<OptionsWire> for Options {
python_downloads, python_downloads,
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,
python_downloads_json_url,
concurrent_downloads, concurrent_downloads,
concurrent_builds, concurrent_builds,
concurrent_installs, concurrent_installs,
@ -1967,6 +1988,7 @@ impl From<OptionsWire> for Options {
install_mirrors: PythonInstallMirrors::resolve( install_mirrors: PythonInstallMirrors::resolve(
python_install_mirror, python_install_mirror,
pypy_install_mirror, pypy_install_mirror,
python_downloads_json_url,
), ),
conflicts, conflicts,
publish: PublishOptions { publish: PublishOptions {

View File

@ -484,6 +484,7 @@ async fn build_package(
Some(&PythonDownloadReporter::single(printer)), Some(&PythonDownloadReporter::single(printer)),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -425,6 +425,7 @@ async fn init_project(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();
@ -451,6 +452,7 @@ async fn init_project(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();
@ -516,6 +518,7 @@ async fn init_project(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();
@ -542,6 +545,7 @@ async fn init_project(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -710,6 +710,7 @@ impl ScriptInterpreter {
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();
@ -903,6 +904,7 @@ impl ProjectInterpreter {
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await?; .await?;
@ -2280,6 +2282,7 @@ pub(crate) async fn init_script_python_requirement(
Some(reporter), Some(reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -608,6 +608,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
Some(&download_reporter), Some(&download_reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();
@ -841,6 +842,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
Some(&download_reporter), Some(&download_reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await?; .await?;

View File

@ -43,7 +43,7 @@ struct InstallRequest {
} }
impl InstallRequest { impl InstallRequest {
fn new(request: PythonRequest) -> Result<Self> { fn new(request: PythonRequest, python_downloads_json_url: Option<&str>) -> Result<Self> {
// Make sure the request is a valid download request and fill platform information // Make sure the request is a valid download request and fill platform information
let download_request = PythonDownloadRequest::from_request(&request) let download_request = PythonDownloadRequest::from_request(&request)
.ok_or_else(|| { .ok_or_else(|| {
@ -55,18 +55,20 @@ impl InstallRequest {
.fill()?; .fill()?;
// Find a matching download // Find a matching download
let download = match ManagedPythonDownload::from_request(&download_request) { let download =
Ok(download) => download, match ManagedPythonDownload::from_request(&download_request, python_downloads_json_url)
Err(downloads::Error::NoDownloadFound(request))
if request.libc().is_some_and(Libc::is_musl)
&& request.arch().is_some_and(Arch::is_arm) =>
{ {
return Err(anyhow::anyhow!( Ok(download) => download,
"uv does not yet provide musl Python distributions on aarch64." Err(downloads::Error::NoDownloadFound(request))
)); if request.libc().is_some_and(Libc::is_musl)
} && request.arch().is_some_and(Arch::is_arm) =>
Err(err) => return Err(err.into()), {
}; return Err(anyhow::anyhow!(
"uv does not yet provide musl Python distributions on aarch64."
));
}
Err(err) => return Err(err.into()),
};
Ok(Self { Ok(Self {
request, request,
@ -131,6 +133,7 @@ pub(crate) async fn install(
force: bool, force: bool,
python_install_mirror: Option<String>, python_install_mirror: Option<String>,
pypy_install_mirror: Option<String>, pypy_install_mirror: Option<String>,
python_downloads_json_url: Option<String>,
network_settings: NetworkSettings, network_settings: NetworkSettings,
default: bool, default: bool,
python_downloads: PythonDownloads, python_downloads: PythonDownloads,
@ -171,13 +174,13 @@ pub(crate) async fn install(
}] }]
}) })
.into_iter() .into_iter()
.map(InstallRequest::new) .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref()))
.collect::<Result<Vec<_>>>()? .collect::<Result<Vec<_>>>()?
} else { } else {
targets targets
.iter() .iter()
.map(|target| PythonRequest::parse(target.as_str())) .map(|target| PythonRequest::parse(target.as_str()))
.map(InstallRequest::new) .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref()))
.collect::<Result<Vec<_>>>()? .collect::<Result<Vec<_>>>()?
}; };
@ -219,7 +222,10 @@ pub(crate) async fn install(
changelog.existing.insert(installation.key().clone()); changelog.existing.insert(installation.key().clone());
if matches!(&request.request, &PythonRequest::Any) { if matches!(&request.request, &PythonRequest::Any) {
// Construct an install request matching the existing installation // Construct an install request matching the existing installation
match InstallRequest::new(PythonRequest::Key(installation.into())) { match InstallRequest::new(
PythonRequest::Key(installation.into()),
python_downloads_json_url.as_deref(),
) {
Ok(request) => { Ok(request) => {
debug!("Will reinstall `{}`", installation.key().green()); debug!("Will reinstall `{}`", installation.key().green());
unsatisfied.push(Cow::Owned(request)); unsatisfied.push(Cow::Owned(request));

View File

@ -59,6 +59,7 @@ pub(crate) async fn list(
all_arches: bool, all_arches: bool,
show_urls: bool, show_urls: bool,
output_format: PythonListFormat, output_format: PythonListFormat,
python_downloads_json_url: Option<String>,
python_preference: PythonPreference, python_preference: PythonPreference,
python_downloads: PythonDownloads, python_downloads: PythonDownloads,
cache: &Cache, cache: &Cache,
@ -101,7 +102,7 @@ pub(crate) async fn list(
let downloads = download_request let downloads = download_request
.as_ref() .as_ref()
.map(PythonDownloadRequest::iter_downloads) .map(|a| PythonDownloadRequest::iter_downloads(a, python_downloads_json_url.as_deref()))
.transpose()? .transpose()?
.into_iter() .into_iter()
.flatten(); .flatten();

View File

@ -150,6 +150,7 @@ pub(crate) async fn refine_interpreter(
Some(reporter), Some(reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -84,6 +84,7 @@ pub(crate) async fn install(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -724,6 +724,7 @@ async fn get_or_create_environment(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(); .into_interpreter();

View File

@ -98,6 +98,7 @@ pub(crate) async fn upgrade(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await? .await?
.into_interpreter(), .into_interpreter(),

View File

@ -222,6 +222,7 @@ async fn venv_impl(
Some(&reporter), Some(&reporter),
install_mirrors.python_install_mirror.as_deref(), install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(), install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
) )
.await .await
.into_diagnostic()?; .into_diagnostic()?;

View File

@ -1333,6 +1333,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.all_arches, args.all_arches,
args.show_urls, args.show_urls,
args.output_format, args.output_format,
args.python_downloads_json_url,
globals.python_preference, globals.python_preference,
globals.python_downloads, globals.python_downloads,
&cache, &cache,
@ -1355,6 +1356,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.force, args.force,
args.python_install_mirror, args.python_install_mirror,
args.pypy_install_mirror, args.pypy_install_mirror,
args.python_downloads_json_url,
globals.network_settings, globals.network_settings,
args.default, args.default,
globals.python_downloads, globals.python_downloads,

View File

@ -849,12 +849,13 @@ pub(crate) struct PythonListSettings {
pub(crate) all_versions: bool, pub(crate) all_versions: bool,
pub(crate) show_urls: bool, pub(crate) show_urls: bool,
pub(crate) output_format: PythonListFormat, pub(crate) output_format: PythonListFormat,
pub(crate) python_downloads_json_url: Option<String>,
} }
impl PythonListSettings { impl PythonListSettings {
/// Resolve the [`PythonListSettings`] from the CLI and filesystem configuration. /// Resolve the [`PythonListSettings`] from the CLI and filesystem configuration.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: PythonListArgs, _filesystem: Option<FilesystemOptions>) -> Self { pub(crate) fn resolve(args: PythonListArgs, filesystem: Option<FilesystemOptions>) -> Self {
let PythonListArgs { let PythonListArgs {
request, request,
all_versions, all_versions,
@ -864,8 +865,18 @@ impl PythonListSettings {
only_downloads, only_downloads,
show_urls, show_urls,
output_format, output_format,
python_downloads_json_url: python_downloads_json_url_arg,
} = args; } = args;
let options = filesystem.map(FilesystemOptions::into_options);
let python_downloads_json_url_option = match options {
Some(options) => options.install_mirrors.python_downloads_json_url,
None => None,
};
let python_downloads_json_url =
python_downloads_json_url_arg.or(python_downloads_json_url_option);
let kinds = if only_installed { let kinds = if only_installed {
PythonListKinds::Installed PythonListKinds::Installed
} else if only_downloads { } else if only_downloads {
@ -882,6 +893,7 @@ impl PythonListSettings {
all_versions, all_versions,
show_urls, show_urls,
output_format, output_format,
python_downloads_json_url,
} }
} }
} }
@ -913,6 +925,7 @@ pub(crate) struct PythonInstallSettings {
pub(crate) force: bool, pub(crate) force: bool,
pub(crate) python_install_mirror: Option<String>, pub(crate) python_install_mirror: Option<String>,
pub(crate) pypy_install_mirror: Option<String>, pub(crate) pypy_install_mirror: Option<String>,
pub(crate) python_downloads_json_url: Option<String>,
pub(crate) default: bool, pub(crate) default: bool,
} }
@ -921,15 +934,18 @@ impl PythonInstallSettings {
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: PythonInstallArgs, filesystem: Option<FilesystemOptions>) -> Self { pub(crate) fn resolve(args: PythonInstallArgs, filesystem: Option<FilesystemOptions>) -> Self {
let options = filesystem.map(FilesystemOptions::into_options); let options = filesystem.map(FilesystemOptions::into_options);
let (python_mirror, pypy_mirror) = match options { let (python_mirror, pypy_mirror, python_downloads_json_url) = match options {
Some(options) => ( Some(options) => (
options.install_mirrors.python_install_mirror, options.install_mirrors.python_install_mirror,
options.install_mirrors.pypy_install_mirror, options.install_mirrors.pypy_install_mirror,
options.install_mirrors.python_downloads_json_url,
), ),
None => (None, None), None => (None, None, None),
}; };
let python_mirror = args.mirror.or(python_mirror); let python_mirror = args.mirror.or(python_mirror);
let pypy_mirror = args.pypy_mirror.or(pypy_mirror); let pypy_mirror = args.pypy_mirror.or(pypy_mirror);
let python_downloads_json_url =
args.python_downloads_json_url.or(python_downloads_json_url);
let PythonInstallArgs { let PythonInstallArgs {
install_dir, install_dir,
@ -938,6 +954,7 @@ impl PythonInstallSettings {
force, force,
mirror: _, mirror: _,
pypy_mirror: _, pypy_mirror: _,
python_downloads_json_url: _,
default, default,
} = args; } = args;
@ -948,6 +965,7 @@ impl PythonInstallSettings {
force, force,
python_install_mirror: python_mirror, python_install_mirror: python_mirror,
pypy_install_mirror: pypy_mirror, pypy_install_mirror: pypy_mirror,
python_downloads_json_url,
default, default,
} }
} }

View File

@ -519,6 +519,13 @@ fn help_subsubcommand() {
[env: UV_PYPY_INSTALL_MIRROR=] [env: UV_PYPY_INSTALL_MIRROR=]
--python-downloads-json-url <PYTHON_DOWNLOADS_JSON_URL>
URL pointing to JSON of custom Python installations.
Note that currently, only local paths are supported.
[env: UV_PYTHON_DOWNLOADS_JSON_URL=]
-r, --reinstall -r, --reinstall
Reinstall the requested Python version, if it's already installed. Reinstall the requested Python version, if it's already installed.
@ -772,15 +779,22 @@ fn help_flag_subsubcommand() {
[TARGETS]... The Python version(s) to install [env: UV_PYTHON=] [TARGETS]... The Python version(s) to install [env: UV_PYTHON=]
Options: Options:
-i, --install-dir <INSTALL_DIR> The directory to store the Python installation in [env: -i, --install-dir <INSTALL_DIR>
UV_PYTHON_INSTALL_DIR=] The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=]
--mirror <MIRROR> Set the URL to use as the source for downloading Python --mirror <MIRROR>
installations [env: UV_PYTHON_INSTALL_MIRROR=] Set the URL to use as the source for downloading Python installations [env:
--pypy-mirror <PYPY_MIRROR> Set the URL to use as the source for downloading PyPy UV_PYTHON_INSTALL_MIRROR=]
installations [env: UV_PYPY_INSTALL_MIRROR=] --pypy-mirror <PYPY_MIRROR>
-r, --reinstall Reinstall the requested Python version, if it's already installed Set the URL to use as the source for downloading PyPy installations [env:
-f, --force Replace existing Python executables during installation UV_PYPY_INSTALL_MIRROR=]
--default Use as the default Python version --python-downloads-json-url <PYTHON_DOWNLOADS_JSON_URL>
URL pointing to JSON of custom Python installations [env: UV_PYTHON_DOWNLOADS_JSON_URL=]
-r, --reinstall
Reinstall the requested Python version, if it's already installed
-f, --force
Replace existing Python executables during installation
--default
Use as the default Python version
Cache options: Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary

View File

@ -148,6 +148,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -328,6 +329,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -509,6 +511,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -722,6 +725,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -871,6 +875,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -1063,6 +1068,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -1302,6 +1308,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -1550,6 +1557,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -1755,6 +1763,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -1926,6 +1935,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -2155,6 +2165,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -2367,6 +2378,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -2537,6 +2549,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -2691,6 +2704,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -2845,6 +2859,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -3001,6 +3016,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -3231,6 +3247,7 @@ fn resolve_tool() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
} }
@ -3340,6 +3357,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -3555,6 +3573,7 @@ fn resolve_both() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -3860,6 +3879,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -3967,7 +3987,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
| |
1 | [project] 1 | [project]
| ^^^^^^^ | ^^^^^^^
unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend` unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend`
"### "###
); );
@ -4108,6 +4128,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -4265,6 +4286,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -4441,6 +4463,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -4676,6 +4699,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -4890,6 +4914,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -5110,6 +5135,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -5325,6 +5351,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -5547,6 +5574,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -5762,6 +5790,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -5923,6 +5952,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -6070,6 +6100,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -6215,6 +6246,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -6362,6 +6394,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -6507,6 +6540,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(
@ -6653,6 +6687,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
python_install_mirror: None, python_install_mirror: None,
pypy_install_mirror: None, pypy_install_mirror: None,
python_downloads_json_url: None,
}, },
system: false, system: false,
extras: ExtrasSpecification( extras: ExtrasSpecification(

View File

@ -4770,6 +4770,11 @@ uv python list [OPTIONS] [REQUEST]
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p> <p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p> <p>May also be set with the <code>UV_PROJECT</code> environment variable.</p>
</dd><dt id="uv-python-list--python-downloads-json-url"><a href="#uv-python-list--python-downloads-json-url"><code>--python-downloads-json-url</code></a> <i>python-downloads-json-url</i></dt><dd><p>URL pointing to JSON of custom Python installations.</p>
<p>Note that currently, only local paths are supported.</p>
<p>May also be set with the <code>UV_PYTHON_DOWNLOADS_JSON_URL</code> environment variable.</p>
</dd><dt id="uv-python-list--quiet"><a href="#uv-python-list--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p> </dd><dt id="uv-python-list--quiet"><a href="#uv-python-list--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p> <p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
@ -4941,6 +4946,11 @@ uv python install [OPTIONS] [TARGETS]...
<p>Distributions can be read from a local directory by using the <code>file://</code> URL scheme.</p> <p>Distributions can be read from a local directory by using the <code>file://</code> URL scheme.</p>
<p>May also be set with the <code>UV_PYPY_INSTALL_MIRROR</code> environment variable.</p> <p>May also be set with the <code>UV_PYPY_INSTALL_MIRROR</code> environment variable.</p>
</dd><dt id="uv-python-install--python-downloads-json-url"><a href="#uv-python-install--python-downloads-json-url"><code>--python-downloads-json-url</code></a> <i>python-downloads-json-url</i></dt><dd><p>URL pointing to JSON of custom Python installations.</p>
<p>Note that currently, only local paths are supported.</p>
<p>May also be set with the <code>UV_PYTHON_DOWNLOADS_JSON_URL</code> environment variable.</p>
</dd><dt id="uv-python-install--quiet"><a href="#uv-python-install--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p> </dd><dt id="uv-python-install--quiet"><a href="#uv-python-install--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p> <p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>

View File

@ -1655,6 +1655,32 @@ Whether to allow Python downloads.
--- ---
### [`python-downloads-json-url`](#python-downloads-json-url) {: #python-downloads-json-url }
URL pointing to JSON of custom Python installations.
Note that currently, only local paths are supported.
**Default value**: `None`
**Type**: `str`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
python-downloads-json-url = "/etc/uv/python-downloads.json"
```
=== "uv.toml"
```toml
python-downloads-json-url = "/etc/uv/python-downloads.json"
```
---
### [`python-install-mirror`](#python-install-mirror) {: #python-install-mirror } ### [`python-install-mirror`](#python-install-mirror) {: #python-install-mirror }
Mirror URL for downloading managed Python installations. Mirror URL for downloading managed Python installations.

7
uv.schema.json generated
View File

@ -431,6 +431,13 @@
} }
] ]
}, },
"python-downloads-json-url": {
"description": "URL pointing to JSON of custom Python installations.\n\nNote that currently, only local paths are supported.",
"type": [
"string",
"null"
]
},
"python-install-mirror": { "python-install-mirror": {
"description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone). This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", "description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone). This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.",
"type": [ "type": [