Add python-downloads-json-url option for uv.toml to configure custom Python installations via JSON URL (#12974)
Some checks are pending
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## 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.
#[arg(long, value_enum, default_value_t = PythonListFormat::default())]
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)]
@ -4791,6 +4797,12 @@ pub struct PythonInstallArgs {
#[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)]
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.
///
/// 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.
pub fn iter_downloads(
&self,
python_downloads_json_url: Option<&str>,
) -> 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)))
}
@ -496,8 +497,9 @@ impl ManagedPythonDownload {
/// be searched for — even if a pre-release was not explicitly requested.
pub fn from_request(
request: &PythonDownloadRequest,
python_downloads_json_url: Option<&str>,
) -> 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);
}
@ -505,7 +507,7 @@ impl ManagedPythonDownload {
if let Some(download) = request
.clone()
.with_prereleases(true)
.iter_downloads()?
.iter_downloads(python_downloads_json_url)?
.next()
{
return Ok(download);
@ -514,32 +516,36 @@ impl ManagedPythonDownload {
Err(Error::NoDownloadFound(request.clone()))
}
//noinspection RsUnresolvedPath - RustRover can't see through the `include!`
/// 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 json_downloads: HashMap<String, JsonPythonDownload> =
if let Ok(json_source) = &runtime_source {
if Url::parse(json_source).is_ok() {
return Err(Error::RemoteJSONNotSupported());
}
let json_downloads: HashMap<String, JsonPythonDownload> = if let Some(json_source) =
python_downloads_json_url
{
if Url::parse(json_source).is_ok() {
return Err(Error::RemoteJSONNotSupported());
}
let file = match fs_err::File::open(json_source) {
Ok(file) => file,
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)
})?
let file = match fs_err::File::open(json_source) {
Ok(file) => file,
Err(e) => { Err(Error::Io(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);
Ok(Cow::Owned(result))
})?;

View file

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

View file

@ -819,21 +819,40 @@ pub struct PythonInstallMirrors {
"#
)]
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 {
fn default() -> Self {
PythonInstallMirrors::resolve(None, None)
PythonInstallMirrors::resolve(None, None, None)
}
}
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 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 {
python_install_mirror: python_mirror_env.or(python_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,
python_install_mirror: Option<String>,
pypy_install_mirror: Option<String>,
python_downloads_json_url: Option<String>,
// #[serde(flatten)]
// publish: PublishOptions
@ -1861,6 +1881,7 @@ impl From<OptionsWire> for Options {
python_downloads,
python_install_mirror,
pypy_install_mirror,
python_downloads_json_url,
concurrent_downloads,
concurrent_builds,
concurrent_installs,
@ -1967,6 +1988,7 @@ impl From<OptionsWire> for Options {
install_mirrors: PythonInstallMirrors::resolve(
python_install_mirror,
pypy_install_mirror,
python_downloads_json_url,
),
conflicts,
publish: PublishOptions {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -519,6 +519,13 @@ fn help_subsubcommand() {
[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
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=]
Options:
-i, --install-dir <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
installations [env: UV_PYTHON_INSTALL_MIRROR=]
--pypy-mirror <PYPY_MIRROR> Set the URL to use as the source for downloading PyPy
installations [env: UV_PYPY_INSTALL_MIRROR=]
-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
-i, --install-dir <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 installations [env:
UV_PYTHON_INSTALL_MIRROR=]
--pypy-mirror <PYPY_MIRROR>
Set the URL to use as the source for downloading PyPy installations [env:
UV_PYPY_INSTALL_MIRROR=]
--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:
-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 {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -328,6 +329,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -509,6 +511,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -722,6 +725,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -871,6 +875,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -1063,6 +1068,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -1302,6 +1308,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -1550,6 +1557,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -1755,6 +1763,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -1926,6 +1935,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -2155,6 +2165,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -2367,6 +2378,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -2537,6 +2549,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -2691,6 +2704,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -2845,6 +2859,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -3001,6 +3016,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -3231,6 +3247,7 @@ fn resolve_tool() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_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 {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -3555,6 +3573,7 @@ fn resolve_both() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -3860,6 +3879,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -3967,7 +3987,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
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 {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -4265,6 +4286,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -4441,6 +4463,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -4676,6 +4699,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -4890,6 +4914,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -5110,6 +5135,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -5325,6 +5351,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -5547,6 +5574,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -5762,6 +5790,7 @@ fn index_priority() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -5923,6 +5952,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -6070,6 +6100,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -6215,6 +6246,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -6362,6 +6394,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -6507,6 +6540,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
@ -6653,6 +6687,7 @@ fn verify_hashes() -> anyhow::Result<()> {
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
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>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>
<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>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>
<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 }
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": {
"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": [