From 372283c0cf3159ae77e644531bc693e714a2b21c Mon Sep 17 00:00:00 2001 From: Andrei Berenda Date: Thu, 25 Sep 2025 19:35:09 +0400 Subject: [PATCH] Add install_mirrors to EnvironmentOptions (#15937) ## Summary Add install_mirrors to EnvironmentOptions Relates #14720 ## Test Plan Tests with existing tests --- crates/uv-cli/src/lib.rs | 37 ++- crates/uv-settings/src/lib.rs | 33 +++ crates/uv-settings/src/settings.rs | 32 +-- crates/uv/src/lib.rs | 60 ++--- crates/uv/src/settings.rs | 327 +++++++++++++++++++-------- crates/uv/tests/it/common/mod.rs | 9 + crates/uv/tests/it/help.rs | 14 +- crates/uv/tests/it/python_install.rs | 1 + crates/uv/tests/it/python_upgrade.rs | 1 + docs/reference/cli.md | 14 +- 10 files changed, 366 insertions(+), 162 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 0ee5a848e..2c43f7717 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -28,6 +28,7 @@ use uv_resolver::{ AnnotationStyle, ExcludeNewerPackageEntry, ExcludeNewerTimestamp, ForkStrategy, PrereleaseMode, ResolutionMode, }; +use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; use uv_torch::TorchMode; use uv_workspace::pyproject_mut::AddBoundsKind; @@ -5363,7 +5364,7 @@ pub struct PythonListArgs { /// 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)] + #[arg(long)] pub python_downloads_json_url: Option, } @@ -5446,7 +5447,7 @@ pub struct PythonInstallArgs { /// `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`. /// /// Distributions can be read from a local directory by using the `file://` URL scheme. - #[arg(long, env = EnvVars::UV_PYTHON_INSTALL_MIRROR)] + #[arg(long)] pub mirror: Option, /// Set the URL to use as the source for downloading PyPy installations. @@ -5455,13 +5456,13 @@ pub struct PythonInstallArgs { /// `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`. /// /// Distributions can be read from a local directory by using the `file://` URL scheme. - #[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)] + #[arg(long)] pub pypy_mirror: Option, /// 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)] + #[arg(long)] pub python_downloads_json_url: Option, /// Reinstall the requested Python version, if it's already installed. @@ -5494,6 +5495,17 @@ pub struct PythonInstallArgs { pub default: bool, } +impl PythonInstallArgs { + #[must_use] + pub fn install_mirrors(&self) -> PythonInstallMirrors { + PythonInstallMirrors { + python_install_mirror: self.mirror.clone(), + pypy_install_mirror: self.pypy_mirror.clone(), + python_downloads_json_url: self.python_downloads_json_url.clone(), + } + } +} + #[derive(Args)] pub struct PythonUpgradeArgs { /// The directory Python installations are stored in. @@ -5519,7 +5531,7 @@ pub struct PythonUpgradeArgs { /// `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`. /// /// Distributions can be read from a local directory by using the `file://` URL scheme. - #[arg(long, env = EnvVars::UV_PYTHON_INSTALL_MIRROR)] + #[arg(long)] pub mirror: Option, /// Set the URL to use as the source for downloading PyPy installations. @@ -5528,7 +5540,7 @@ pub struct PythonUpgradeArgs { /// `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`. /// /// Distributions can be read from a local directory by using the `file://` URL scheme. - #[arg(long, env = EnvVars::UV_PYPY_INSTALL_MIRROR)] + #[arg(long)] pub pypy_mirror: Option, /// Reinstall the latest Python patch, if it's already installed. @@ -5541,10 +5553,21 @@ pub struct PythonUpgradeArgs { /// 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)] + #[arg(long)] pub python_downloads_json_url: Option, } +impl PythonUpgradeArgs { + #[must_use] + pub fn install_mirrors(&self) -> PythonInstallMirrors { + PythonInstallMirrors { + python_install_mirror: self.mirror.clone(), + pypy_install_mirror: self.pypy_mirror.clone(), + python_downloads_json_url: self.python_downloads_json_url.clone(), + } + } +} + #[derive(Args)] pub struct PythonUninstallArgs { /// The directory where the Python was installed. diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index df15d60bf..49eb5927c 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -570,6 +570,7 @@ pub enum Error { pub struct EnvironmentOptions { pub python_install_bin: Option, pub python_install_registry: Option, + pub install_mirrors: PythonInstallMirrors, } impl EnvironmentOptions { @@ -580,6 +581,17 @@ impl EnvironmentOptions { python_install_registry: parse_boolish_environment_variable( EnvVars::UV_PYTHON_INSTALL_REGISTRY, )?, + install_mirrors: PythonInstallMirrors { + python_install_mirror: parse_string_environment_variable( + EnvVars::UV_PYTHON_INSTALL_MIRROR, + )?, + pypy_install_mirror: parse_string_environment_variable( + EnvVars::UV_PYPY_INSTALL_MIRROR, + )?, + python_downloads_json_url: parse_string_environment_variable( + EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL, + )?, + }, }) } } @@ -635,3 +647,24 @@ fn parse_boolish_environment_variable(name: &'static str) -> Result Ok(Some(value)) } + +/// Parse a string environment variable. +fn parse_string_environment_variable(name: &'static str) -> Result, Error> { + match std::env::var(name) { + Ok(v) => { + if v.is_empty() { + Ok(None) + } else { + Ok(Some(v)) + } + } + Err(e) => match e { + std::env::VarError::NotPresent => Ok(None), + std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable { + name: name.to_string(), + value: err.to_string_lossy().to_string(), + err: "expected a valid UTF-8 string".to_string(), + }), + }, + } +} diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index df6ad260a..6042af68d 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -22,7 +22,6 @@ use uv_resolver::{ AnnotationStyle, ExcludeNewer, ExcludeNewerPackage, ExcludeNewerTimestamp, ForkStrategy, PrereleaseMode, ResolutionMode, }; -use uv_static::EnvVars; use uv_torch::TorchMode; use uv_workspace::pyproject::ExtraBuildDependencies; use uv_workspace::pyproject_mut::AddBoundsKind; @@ -958,7 +957,7 @@ pub struct ResolverInstallerSchema { } /// Shared settings, relevant to all operations that might create managed python installations. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PythonInstallMirrors { @@ -1007,26 +1006,15 @@ pub struct PythonInstallMirrors { pub python_downloads_json_url: Option, } -impl Default for PythonInstallMirrors { - fn default() -> Self { - Self::resolve(None, None, None) - } -} - impl PythonInstallMirrors { - pub fn resolve( - python_mirror: Option, - pypy_mirror: Option, - python_downloads_json_url: Option, - ) -> 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(); + #[must_use] + pub fn combine(self, other: Self) -> Self { Self { - 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), + python_install_mirror: self.python_install_mirror.or(other.python_install_mirror), + pypy_install_mirror: self.pypy_install_mirror.or(other.pypy_install_mirror), + python_downloads_json_url: self + .python_downloads_json_url + .or(other.python_downloads_json_url), } } } @@ -2270,11 +2258,11 @@ impl From for Options { build_constraint_dependencies, environments, required_environments, - install_mirrors: PythonInstallMirrors::resolve( + install_mirrors: PythonInstallMirrors { python_install_mirror, pypy_install_mirror, python_downloads_json_url, - ), + }, conflicts, publish: PublishOptions { publish_url, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index bfd00ab66..3da0606c2 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -523,7 +523,7 @@ async fn run(mut cli: Cli) -> Result { args.compat_args.validate()?; // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipCompileSettings::resolve(args, filesystem); + let args = PipCompileSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -624,7 +624,7 @@ async fn run(mut cli: Cli) -> Result { args.compat_args.validate()?; // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipSyncSettings::resolve(args, filesystem); + let args = PipSyncSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -703,7 +703,7 @@ async fn run(mut cli: Cli) -> Result { args.compat_args.validate()?; // Resolve the settings from the command-line arguments and workspace configuration. - let mut args = PipInstallSettings::resolve(args, filesystem); + let mut args = PipInstallSettings::resolve(args, filesystem, environment); show_settings!(args); let mut requirements = Vec::with_capacity( @@ -845,7 +845,7 @@ async fn run(mut cli: Cli) -> Result { command: PipCommand::Uninstall(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipUninstallSettings::resolve(args, filesystem); + let args = PipUninstallSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -881,7 +881,7 @@ async fn run(mut cli: Cli) -> Result { command: PipCommand::Freeze(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipFreezeSettings::resolve(args, filesystem); + let args = PipFreezeSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -904,7 +904,7 @@ async fn run(mut cli: Cli) -> Result { args.compat_args.validate()?; // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipListSettings::resolve(args, filesystem); + let args = PipListSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -935,7 +935,7 @@ async fn run(mut cli: Cli) -> Result { command: PipCommand::Show(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipShowSettings::resolve(args, filesystem); + let args = PipShowSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -956,7 +956,7 @@ async fn run(mut cli: Cli) -> Result { command: PipCommand::Tree(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipTreeSettings::resolve(args, filesystem); + let args = PipTreeSettings::resolve(args, filesystem, environment); // Initialize the cache. let cache = cache.init()?; @@ -989,7 +989,7 @@ async fn run(mut cli: Cli) -> Result { command: PipCommand::Check(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = PipCheckSettings::resolve(args, filesystem); + let args = PipCheckSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1026,7 +1026,7 @@ async fn run(mut cli: Cli) -> Result { } Commands::Build(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::BuildSettings::resolve(args, filesystem); + let args = settings::BuildSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1085,7 +1085,7 @@ async fn run(mut cli: Cli) -> Result { } // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::VenvSettings::resolve(args, filesystem); + let args = settings::VenvSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1222,7 +1222,12 @@ async fn run(mut cli: Cli) -> Result { } // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::ToolRunSettings::resolve(args, filesystem, invocation_source); + let args = settings::ToolRunSettings::resolve( + args, + filesystem, + invocation_source, + environment, + ); show_settings!(args); // Initialize the cache. @@ -1299,7 +1304,7 @@ async fn run(mut cli: Cli) -> Result { command: ToolCommand::Install(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::ToolInstallSettings::resolve(args, filesystem); + let args = settings::ToolInstallSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1405,7 +1410,7 @@ async fn run(mut cli: Cli) -> Result { command: ToolCommand::Upgrade(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::ToolUpgradeSettings::resolve(args, filesystem); + let args = settings::ToolUpgradeSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1458,7 +1463,7 @@ async fn run(mut cli: Cli) -> Result { command: PythonCommand::List(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::PythonListSettings::resolve(args, filesystem); + let args = settings::PythonListSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1515,7 +1520,7 @@ async fn run(mut cli: Cli) -> Result { command: PythonCommand::Upgrade(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::PythonUpgradeSettings::resolve(args, filesystem); + let args = settings::PythonUpgradeSettings::resolve(args, filesystem, environment); show_settings!(args); let upgrade = true; @@ -1598,7 +1603,7 @@ async fn run(mut cli: Cli) -> Result { command: PythonCommand::Pin(args), }) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::PythonPinSettings::resolve(args, filesystem); + let args = settings::PythonPinSettings::resolve(args, filesystem, environment); // Initialize the cache. let cache = cache.init()?; @@ -1744,10 +1749,13 @@ async fn run_project( }; } + // Load environment variables not handled by Clap + let environment = EnvironmentOptions::new()?; + match *project_command { ProjectCommand::Init(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::InitSettings::resolve(args, filesystem); + let args = settings::InitSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1782,7 +1790,7 @@ async fn run_project( } ProjectCommand::Run(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::RunSettings::resolve(args, filesystem); + let args = settings::RunSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1846,7 +1854,7 @@ async fn run_project( } ProjectCommand::Sync(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::SyncSettings::resolve(args, filesystem); + let args = settings::SyncSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1896,7 +1904,7 @@ async fn run_project( } ProjectCommand::Lock(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::LockSettings::resolve(args, filesystem); + let args = settings::LockSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -1942,7 +1950,7 @@ async fn run_project( } ProjectCommand::Add(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let mut args = settings::AddSettings::resolve(args, filesystem); + let mut args = settings::AddSettings::resolve(args, filesystem, environment); show_settings!(args); // If the script already exists, use it; otherwise, propagate the file path and we'll @@ -2065,7 +2073,7 @@ async fn run_project( } ProjectCommand::Remove(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::RemoveSettings::resolve(args, filesystem); + let args = settings::RemoveSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -2109,7 +2117,7 @@ async fn run_project( } ProjectCommand::Version(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::VersionSettings::resolve(args, filesystem); + let args = settings::VersionSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -2149,7 +2157,7 @@ async fn run_project( } ProjectCommand::Tree(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::TreeSettings::resolve(args, filesystem); + let args = settings::TreeSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. @@ -2194,7 +2202,7 @@ async fn run_project( } ProjectCommand::Export(args) => { // Resolve the settings from the command-line arguments and workspace configuration. - let args = settings::ExportSettings::resolve(args, filesystem); + let args = settings::ExportSettings::resolve(args, filesystem, environment); show_settings!(args); // Initialize the cache. diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ece441099..b8826bda6 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -254,7 +254,11 @@ pub(crate) struct InitSettings { impl InitSettings { /// Resolve the [`InitSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: InitArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: InitArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let InitArgs { path, name, @@ -293,7 +297,7 @@ impl InitSettings { ) .unwrap_or(kind.packaged_by_default()); - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -314,7 +318,9 @@ impl InitSettings { pin_python: flag(pin_python, no_pin_python, "pin-python").unwrap_or(!bare), no_workspace, python: python.and_then(Maybe::into_option), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -356,7 +362,11 @@ impl RunSettings { /// Resolve the [`RunSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: RunArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: RunArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let RunArgs { extra, all_extras, @@ -401,7 +411,7 @@ impl RunSettings { max_recursion_depth, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -461,7 +471,9 @@ impl RunSettings { filesystem, ), env_file: EnvFile::from_args(env_file, no_env_file), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), max_recursion_depth: max_recursion_depth.unwrap_or(Self::DEFAULT_MAX_RECURSION_DEPTH), } } @@ -497,6 +509,7 @@ impl ToolRunSettings { args: ToolRunArgs, filesystem: Option, invocation_source: ToolRunCommand, + environment: EnvironmentOptions, ) -> Self { let ToolRunArgs { command, @@ -554,7 +567,7 @@ impl ToolRunSettings { .unwrap_or_default(), )); - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .map(FilesystemOptions::into_options) .map(|options| options.install_mirrors) .unwrap_or_default(); @@ -595,7 +608,9 @@ impl ToolRunSettings { refresh: Refresh::from(refresh), settings, options, - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), env_file, no_env_file, } @@ -627,7 +642,11 @@ pub(crate) struct ToolInstallSettings { impl ToolInstallSettings { /// Resolve the [`ToolInstallSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: ToolInstallArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: ToolInstallArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let ToolInstallArgs { package, editable, @@ -656,7 +675,7 @@ impl ToolInstallSettings { .unwrap_or_default(), )); - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .map(FilesystemOptions::into_options) .map(|options| options.install_mirrors) .unwrap_or_default(); @@ -701,7 +720,9 @@ impl ToolInstallSettings { refresh: Refresh::from(refresh), options, settings, - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -719,7 +740,11 @@ pub(crate) struct ToolUpgradeSettings { impl ToolUpgradeSettings { /// Resolve the [`ToolUpgradeSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: ToolUpgradeArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: ToolUpgradeArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let ToolUpgradeArgs { name, python, @@ -788,7 +813,7 @@ impl ToolUpgradeSettings { let args = resolver_installer_options(installer, build); let filesystem = filesystem.map(FilesystemOptions::into_options); - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|options| options.install_mirrors) .unwrap_or_default(); @@ -804,7 +829,9 @@ impl ToolUpgradeSettings { python_platform, args, filesystem: top_level, - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -900,7 +927,11 @@ pub(crate) struct PythonListSettings { 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) -> Self { + pub(crate) fn resolve( + args: PythonListArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PythonListArgs { request, all_versions, @@ -919,8 +950,9 @@ impl PythonListSettings { None => None, }; - let python_downloads_json_url = - python_downloads_json_url_arg.or(python_downloads_json_url_option); + let python_downloads_json_url = python_downloads_json_url_arg + .or(environment.install_mirrors.python_downloads_json_url) + .or(python_downloads_json_url_option); let kinds = if only_installed { PythonListKinds::Installed @@ -982,19 +1014,20 @@ impl PythonInstallSettings { filesystem: Option, environment: EnvironmentOptions, ) -> Self { - let options = filesystem.map(FilesystemOptions::into_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), - }; - 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 filesystem_install_mirrors = filesystem + .map(|fs| fs.install_mirrors.clone()) + .unwrap_or_default(); + + let install_mirrors = args + .install_mirrors() + .combine(environment.install_mirrors) + .combine(filesystem_install_mirrors); + + let PythonInstallMirrors { + python_install_mirror, + pypy_install_mirror, + python_downloads_json_url, + } = install_mirrors; let PythonInstallArgs { install_dir, @@ -1019,8 +1052,8 @@ impl PythonInstallSettings { bin: flag(bin, no_bin, "bin").or(environment.python_install_bin), registry: flag(registry, no_registry, "registry") .or(environment.python_install_registry), - python_install_mirror: python_mirror, - pypy_install_mirror: pypy_mirror, + python_install_mirror, + pypy_install_mirror, python_downloads_json_url, default, } @@ -1046,20 +1079,26 @@ pub(crate) struct PythonUpgradeSettings { impl PythonUpgradeSettings { /// Resolve the [`PythonUpgradeSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: PythonUpgradeArgs, filesystem: Option) -> Self { - let options = filesystem.map(FilesystemOptions::into_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), - }; - 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); + pub(crate) fn resolve( + args: PythonUpgradeArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { + let filesystem_install_mirrors = filesystem + .map(|fs| fs.install_mirrors.clone()) + .unwrap_or_default(); + + let install_mirrors = args + .install_mirrors() + .combine(environment.install_mirrors) + .combine(filesystem_install_mirrors); + + let PythonInstallMirrors { + python_install_mirror, + pypy_install_mirror, + python_downloads_json_url, + } = install_mirrors; + let force = false; let default = false; let bin = None; @@ -1079,8 +1118,8 @@ impl PythonUpgradeSettings { targets, force, registry, - python_install_mirror: python_mirror, - pypy_install_mirror: pypy_mirror, + python_install_mirror, + pypy_install_mirror, reinstall, python_downloads_json_url, default, @@ -1163,7 +1202,11 @@ pub(crate) struct PythonPinSettings { impl PythonPinSettings { /// Resolve the [`PythonPinSettings`] from the CLI and workspace configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: PythonPinArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PythonPinArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PythonPinArgs { request, no_resolved, @@ -1173,7 +1216,7 @@ impl PythonPinSettings { rm, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1183,7 +1226,9 @@ impl PythonPinSettings { no_project, global, rm, - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1215,7 +1260,11 @@ pub(crate) struct SyncSettings { impl SyncSettings { /// Resolve the [`SyncSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: SyncArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: SyncArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let SyncArgs { extra, all_extras, @@ -1254,7 +1303,7 @@ impl SyncSettings { no_check, output_format, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1315,7 +1364,9 @@ impl SyncSettings { python_platform, refresh: Refresh::from(refresh), settings, - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1337,7 +1388,11 @@ pub(crate) struct LockSettings { impl LockSettings { /// Resolve the [`LockSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: LockArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: LockArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let LockArgs { check, check_exists, @@ -1349,7 +1404,7 @@ impl LockSettings { python, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1362,7 +1417,9 @@ impl LockSettings { python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1403,7 +1460,11 @@ pub(crate) struct AddSettings { impl AddSettings { /// Resolve the [`AddSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: AddArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: AddArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let AddArgs { packages, requirements, @@ -1508,7 +1569,7 @@ impl AddSettings { } } - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .as_ref() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1548,7 +1609,9 @@ impl AddSettings { resolver_installer_options(installer, build), filesystem, ), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1574,7 +1637,11 @@ pub(crate) struct RemoveSettings { impl RemoveSettings { /// Resolve the [`RemoveSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: RemoveArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: RemoveArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let RemoveArgs { dev, optional, @@ -1603,7 +1670,7 @@ impl RemoveSettings { DependencyType::Production }; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1628,7 +1695,9 @@ impl RemoveSettings { resolver_installer_options(installer, build), filesystem, ), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1656,7 +1725,11 @@ pub(crate) struct VersionSettings { impl VersionSettings { /// Resolve the [`RemoveSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: VersionArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: VersionArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let VersionArgs { value, bump, @@ -1675,7 +1748,7 @@ impl VersionSettings { python, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1697,7 +1770,9 @@ impl VersionSettings { resolver_installer_options(installer, build), filesystem, ), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1727,7 +1802,11 @@ pub(crate) struct TreeSettings { impl TreeSettings { /// Resolve the [`TreeSettings`] from the CLI and workspace configuration. - pub(crate) fn resolve(args: TreeArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: TreeArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let TreeArgs { tree, universal, @@ -1748,7 +1827,8 @@ impl TreeSettings { python_platform, python, } = args; - let install_mirrors = filesystem + + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1779,7 +1859,9 @@ impl TreeSettings { python_platform, python: python.and_then(Maybe::into_option), resolver: ResolverSettings::combine(resolver_options(resolver, build), filesystem), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1812,7 +1894,11 @@ pub(crate) struct ExportSettings { impl ExportSettings { /// Resolve the [`ExportSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: ExportArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: ExportArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let ExportArgs { format, all_packages, @@ -1851,7 +1937,7 @@ impl ExportSettings { script, python, } = args; - let install_mirrors = filesystem + let filesystem_install_mirrors = filesystem .clone() .map(|fs| fs.install_mirrors.clone()) .unwrap_or_default(); @@ -1897,7 +1983,9 @@ impl ExportSettings { python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -1951,7 +2039,11 @@ pub(crate) struct PipCompileSettings { impl PipCompileSettings { /// Resolve the [`PipCompileSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipCompileArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipCompileArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipCompileArgs { src_file, constraints, @@ -2117,6 +2209,7 @@ impl PipCompileSettings { ..PipOptions::from(resolver) }, filesystem, + environment, ), } } @@ -2135,7 +2228,11 @@ pub(crate) struct PipSyncSettings { impl PipSyncSettings { /// Resolve the [`PipSyncSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: Box, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: Box, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipSyncArgs { src_file, constraints, @@ -2215,6 +2312,7 @@ impl PipSyncSettings { ..PipOptions::from(installer) }, filesystem, + environment, ), } } @@ -2240,7 +2338,11 @@ pub(crate) struct PipInstallSettings { impl PipInstallSettings { /// Resolve the [`PipInstallSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipInstallArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipInstallArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipInstallArgs { package, requirements, @@ -2377,6 +2479,7 @@ impl PipInstallSettings { ..PipOptions::from(installer) }, filesystem, + environment, ), } } @@ -2393,7 +2496,11 @@ pub(crate) struct PipUninstallSettings { impl PipUninstallSettings { /// Resolve the [`PipUninstallSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipUninstallArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipUninstallArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipUninstallArgs { package, requirements, @@ -2428,6 +2535,7 @@ impl PipUninstallSettings { ..PipOptions::default() }, filesystem, + environment, ), } } @@ -2443,7 +2551,11 @@ pub(crate) struct PipFreezeSettings { impl PipFreezeSettings { /// Resolve the [`PipFreezeSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipFreezeArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipFreezeArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipFreezeArgs { exclude_editable, strict, @@ -2466,6 +2578,7 @@ impl PipFreezeSettings { ..PipOptions::default() }, filesystem, + environment, ), } } @@ -2483,7 +2596,11 @@ pub(crate) struct PipListSettings { impl PipListSettings { /// Resolve the [`PipListSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipListArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipListArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipListArgs { editable, exclude_editable, @@ -2513,6 +2630,7 @@ impl PipListSettings { ..PipOptions::from(fetch) }, filesystem, + environment, ), } } @@ -2528,7 +2646,11 @@ pub(crate) struct PipShowSettings { impl PipShowSettings { /// Resolve the [`PipShowSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipShowArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipShowArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipShowArgs { package, strict, @@ -2551,6 +2673,7 @@ impl PipShowSettings { ..PipOptions::default() }, filesystem, + environment, ), } } @@ -2571,7 +2694,11 @@ pub(crate) struct PipTreeSettings { impl PipTreeSettings { /// Resolve the [`PipTreeSettings`] from the CLI and workspace configuration. - pub(crate) fn resolve(args: PipTreeArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipTreeArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipTreeArgs { show_version_specifiers, tree, @@ -2600,6 +2727,7 @@ impl PipTreeSettings { ..PipOptions::from(fetch) }, filesystem, + environment, ), } } @@ -2613,7 +2741,11 @@ pub(crate) struct PipCheckSettings { impl PipCheckSettings { /// Resolve the [`PipCheckSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: PipCheckArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: PipCheckArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let PipCheckArgs { python, system, @@ -2632,6 +2764,7 @@ impl PipCheckSettings { ..PipOptions::default() }, filesystem, + environment, ), } } @@ -2659,7 +2792,11 @@ pub(crate) struct BuildSettings { impl BuildSettings { /// Resolve the [`BuildSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: BuildArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: BuildArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let BuildArgs { src, out_dir, @@ -2681,8 +2818,7 @@ impl BuildSettings { refresh, resolver, } = args; - - let install_mirrors = match &filesystem { + let filesystem_install_mirrors = match &filesystem { Some(fs) => fs.install_mirrors.clone(), None => PythonInstallMirrors::default(), }; @@ -2708,7 +2844,9 @@ impl BuildSettings { python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } @@ -2731,7 +2869,11 @@ pub(crate) struct VenvSettings { impl VenvSettings { /// Resolve the [`VenvSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: VenvArgs, filesystem: Option) -> Self { + pub(crate) fn resolve( + args: VenvArgs, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let VenvArgs { python, system, @@ -2779,6 +2921,7 @@ impl VenvSettings { ..PipOptions::from(index_args) }, filesystem, + environment, ), } } @@ -3041,11 +3184,15 @@ pub(crate) struct PipSettings { impl PipSettings { /// Resolve the [`PipSettings`] from the CLI and filesystem configuration. - pub(crate) fn combine(args: PipOptions, filesystem: Option) -> Self { + pub(crate) fn combine( + args: PipOptions, + filesystem: Option, + environment: EnvironmentOptions, + ) -> Self { let Options { top_level, pip, - install_mirrors, + install_mirrors: filesystem_install_mirrors, .. } = filesystem .map(FilesystemOptions::into_options) @@ -3386,7 +3533,9 @@ impl PipSettings { top_level_no_build_package.unwrap_or_default(), )), ), - install_mirrors, + install_mirrors: environment + .install_mirrors + .combine(filesystem_install_mirrors), } } } diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 292c5c8bd..6f02086e1 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -466,6 +466,15 @@ impl TestContext { self } + #[must_use] + pub fn with_empty_python_install_mirror(mut self) -> Self { + self.extra_env.push(( + EnvVars::UV_PYTHON_INSTALL_MIRROR.into(), + String::new().into(), + )); + self + } + /// Add extra directories and configuration for managed Python installations. #[must_use] pub fn with_managed_python_dirs(mut self) -> Self { diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 63e4c1525..b52ef2a88 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -529,8 +529,6 @@ fn help_subsubcommand() { `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`. Distributions can be read from a local directory by using the `file://` URL scheme. - - [env: UV_PYTHON_INSTALL_MIRROR=] --pypy-mirror Set the URL to use as the source for downloading PyPy installations. @@ -539,15 +537,11 @@ fn help_subsubcommand() { `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`. Distributions can be read from a local directory by using the `file://` URL scheme. - - [env: UV_PYPY_INSTALL_MIRROR=] --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. @@ -812,13 +806,11 @@ fn help_flag_subsubcommand() { --no-registry Do not register the Python installation in the Windows registry --mirror - Set the URL to use as the source for downloading Python installations [env: - UV_PYTHON_INSTALL_MIRROR=] + Set the URL to use as the source for downloading Python installations --pypy-mirror - Set the URL to use as the source for downloading PyPy installations [env: - UV_PYPY_INSTALL_MIRROR=] + Set the URL to use as the source for downloading PyPy installations --python-downloads-json-url - URL pointing to JSON of custom Python installations [env: UV_PYTHON_DOWNLOADS_JSON_URL=] + URL pointing to JSON of custom Python installations -r, --reinstall Reinstall the requested Python version, if it's already installed -f, --force diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index c8240b6b0..b36d590f4 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -22,6 +22,7 @@ fn python_install() { .with_filtered_python_keys() .with_filtered_exe_suffix() .with_managed_python_dirs() + .with_empty_python_install_mirror() .with_python_download_cache(); // Install the latest version diff --git a/crates/uv/tests/it/python_upgrade.rs b/crates/uv/tests/it/python_upgrade.rs index 663e8b25d..9f00d1c4f 100644 --- a/crates/uv/tests/it/python_upgrade.rs +++ b/crates/uv/tests/it/python_upgrade.rs @@ -703,6 +703,7 @@ fn python_upgrade_force_install() -> Result<()> { let context = TestContext::new_with_versions(&["3.13"]) .with_filtered_python_keys() .with_filtered_exe_suffix() + .with_empty_python_install_mirror() .with_managed_python_dirs(); context diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5f1792e22..98da105b0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3405,7 +3405,7 @@ uv python list [OPTIONS] [REQUEST]

This setting has no effect when used in the uv pip interface.

May also be set with the UV_PROJECT environment variable.

--python-downloads-json-url python-downloads-json-url

URL pointing to JSON of custom Python installations.

Note that currently, only local paths are supported.

-

May also be set with the UV_PYTHON_DOWNLOADS_JSON_URL environment variable.

--quiet, -q

Use quiet output.

+
--quiet, -q

Use quiet output.

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

--show-urls

Show the URLs of available Python downloads.

By default, these display as <download available>.

@@ -3477,7 +3477,7 @@ uv python install [OPTIONS] [TARGETS]...

May also be set with the UV_MANAGED_PYTHON environment variable.

--mirror mirror

Set the URL to use as the source for downloading 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.

Distributions can be read from a local directory by using the file:// URL scheme.

-

May also be set with the UV_PYTHON_INSTALL_MIRROR environment variable.

--native-tls

Whether to load TLS certificates from the platform's native certificate store.

+
--native-tls

Whether to load TLS certificates from the platform's native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

May also be set with the UV_NATIVE_TLS environment variable.

--no-bin

Do not install a Python executable into the bin directory.

@@ -3502,9 +3502,9 @@ uv python install [OPTIONS] [TARGETS]...

May also be set with the UV_PROJECT environment variable.

--pypy-mirror pypy-mirror

Set the URL to use as the source for downloading PyPy installations.

The provided URL will replace https://downloads.python.org/pypy in, e.g., https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2.

Distributions can be read from a local directory by using the file:// URL scheme.

-

May also be set with the UV_PYPY_INSTALL_MIRROR environment variable.

--python-downloads-json-url python-downloads-json-url

URL pointing to JSON of custom Python installations.

+
--python-downloads-json-url python-downloads-json-url

URL pointing to JSON of custom Python installations.

Note that currently, only local paths are supported.

-

May also be set with the UV_PYTHON_DOWNLOADS_JSON_URL environment variable.

--quiet, -q

Use quiet output.

+
--quiet, -q

Use quiet output.

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

--reinstall, -r

Reinstall the requested Python version, if it's already installed.

By default, uv will exit successfully if the version is already installed.

@@ -3570,7 +3570,7 @@ uv python upgrade [OPTIONS] [TARGETS]...

May also be set with the UV_MANAGED_PYTHON environment variable.

--mirror mirror

Set the URL to use as the source for downloading 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.

Distributions can be read from a local directory by using the file:// URL scheme.

-

May also be set with the UV_PYTHON_INSTALL_MIRROR environment variable.

--native-tls

Whether to load TLS certificates from the platform's native certificate store.

+
--native-tls

Whether to load TLS certificates from the platform's native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.

May also be set with the UV_NATIVE_TLS environment variable.

--no-cache, --no-cache-dir, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

@@ -3591,9 +3591,9 @@ uv python upgrade [OPTIONS] [TARGETS]...

May also be set with the UV_PROJECT environment variable.

--pypy-mirror pypy-mirror

Set the URL to use as the source for downloading PyPy installations.

The provided URL will replace https://downloads.python.org/pypy in, e.g., https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2.

Distributions can be read from a local directory by using the file:// URL scheme.

-

May also be set with the UV_PYPY_INSTALL_MIRROR environment variable.

--python-downloads-json-url python-downloads-json-url

URL pointing to JSON of custom Python installations.

+
--python-downloads-json-url python-downloads-json-url

URL pointing to JSON of custom Python installations.

Note that currently, only local paths are supported.

-

May also be set with the UV_PYTHON_DOWNLOADS_JSON_URL environment variable.

--quiet, -q

Use quiet output.

+
--quiet, -q

Use quiet output.

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

--reinstall, -r

Reinstall the latest Python patch, if it's already installed.

By default, uv will exit successfully if the latest patch is already installed.