diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index f500d3e1d..46bd0bad9 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -1458,7 +1458,7 @@ impl ManagedPythonDownload { /// Return the [`Url`] to use when downloading the distribution. If a mirror is set via the /// appropriate environment variable, use it instead. - fn download_url( + pub fn download_url( &self, python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index 96c500e35..8259b17a0 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -62,6 +62,8 @@ pub(crate) async fn list( show_urls: bool, output_format: PythonListFormat, python_downloads_json_url: Option, + python_install_mirror: Option, + pypy_install_mirror: Option, python_preference: PythonPreference, python_downloads: PythonDownloads, client_builder: &BaseClientBuilder<'_>, @@ -121,7 +123,10 @@ pub(crate) async fn list( output.insert(( download.key().clone(), Kind::Download, - Either::Right(download.url()), + Either::Right(download.download_url( + python_install_mirror.as_deref(), + pypy_install_mirror.as_deref(), + )?), )); } } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index d8d4dbb91..dac02a317 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1585,6 +1585,8 @@ async fn run(mut cli: Cli) -> Result { args.show_urls, args.output_format, args.python_downloads_json_url, + args.python_install_mirror, + args.pypy_install_mirror, globals.python_preference, globals.python_downloads, &client_builder.subcommand(vec!["python".to_owned(), "list".to_owned()]), diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bf879f7b4..85c802c6f 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -995,6 +995,8 @@ pub(crate) struct PythonListSettings { pub(crate) show_urls: bool, pub(crate) output_format: PythonListFormat, pub(crate) python_downloads_json_url: Option, + pub(crate) python_install_mirror: Option, + pub(crate) pypy_install_mirror: Option, } impl PythonListSettings { @@ -1018,15 +1020,38 @@ impl PythonListSettings { } = 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_option, + python_install_mirror_option, + pypy_install_mirror_option, + ) = match &options { + Some(options) => ( + options.install_mirrors.python_downloads_json_url.clone(), + options.install_mirrors.python_install_mirror.clone(), + options.install_mirrors.pypy_install_mirror.clone(), + ), + None => (None, None, None), }; let python_downloads_json_url = python_downloads_json_url_arg - .or(environment.install_mirrors.python_downloads_json_url) + .or(environment + .install_mirrors + .python_downloads_json_url + .clone()) .or(python_downloads_json_url_option); + let python_install_mirror = environment + .install_mirrors + .python_install_mirror + .clone() + .or(python_install_mirror_option); + + let pypy_install_mirror = environment + .install_mirrors + .pypy_install_mirror + .clone() + .or(pypy_install_mirror_option); + let kinds = if only_installed { PythonListKinds::Installed } else if only_downloads { @@ -1044,6 +1069,8 @@ impl PythonListSettings { show_urls, output_format, python_downloads_json_url, + python_install_mirror, + pypy_install_mirror, } } } diff --git a/crates/uv/tests/it/python_list.rs b/crates/uv/tests/it/python_list.rs index 7b77da6e1..ec11424b2 100644 --- a/crates/uv/tests/it/python_list.rs +++ b/crates/uv/tests/it/python_list.rs @@ -591,3 +591,96 @@ async fn python_list_remote_python_downloads_json_url() -> Result<()> { Ok(()) } + +#[test] +fn python_list_with_mirrors() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_collapsed_whitespace() + // Add filters to normalize file paths in URLs + .with_filter(( + r"(https://mirror\.example\.com/).*".to_string(), + "$1[FILE-PATH]".to_string(), + )) + .with_filter(( + r"(https://python-mirror\.example\.com/).*".to_string(), + "$1[FILE-PATH]".to_string(), + )) + .with_filter(( + r"(https://pypy-mirror\.example\.com/).*".to_string(), + "$1[FILE-PATH]".to_string(), + )) + .with_filter(( + r"(https://github\.com/astral-sh/python-build-standalone/releases/download/).*" + .to_string(), + "$1[FILE-PATH]".to_string(), + )) + .with_filter(( + r"(https://downloads\.python\.org/pypy/).*".to_string(), + "$1[FILE-PATH]".to_string(), + )) + .with_filter(( + r"(https://github\.com/oracle/graalpython/releases/download/).*".to_string(), + "$1[FILE-PATH]".to_string(), + )); + + // Test with UV_PYTHON_INSTALL_MIRROR environment variable - verify mirror URL is used + uv_snapshot!(context.filters(), context.python_list() + .arg("cpython@3.10.19") + .arg("--show-urls") + .env(EnvVars::UV_PYTHON_INSTALL_MIRROR, "https://mirror.example.com") + .env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.10.19-[PLATFORM] https://mirror.example.com/[FILE-PATH] + + ----- stderr ----- + "); + + // Test with UV_PYPY_INSTALL_MIRROR environment variable - verify PyPy mirror URL is used + uv_snapshot!(context.filters(), context.python_list() + .arg("pypy@3.10") + .arg("--show-urls") + .env(EnvVars::UV_PYPY_INSTALL_MIRROR, "https://pypy-mirror.example.com") + .env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r" + success: true + exit_code: 0 + ----- stdout ----- + pypy-3.10.16-[PLATFORM] https://pypy-mirror.example.com/[FILE-PATH] + + ----- stderr ----- + "); + + // Test with both mirror environment variables set + uv_snapshot!(context.filters(), context.python_list() + .arg("3.10") + .arg("--show-urls") + .env(EnvVars::UV_PYTHON_INSTALL_MIRROR, "https://python-mirror.example.com") + .env(EnvVars::UV_PYPY_INSTALL_MIRROR, "https://pypy-mirror.example.com") + .env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.10.19-[PLATFORM] https://python-mirror.example.com/[FILE-PATH] + pypy-3.10.16-[PLATFORM] https://pypy-mirror.example.com/[FILE-PATH] + graalpy-3.10.0-[PLATFORM] https://github.com/oracle/graalpython/releases/download/[FILE-PATH] + + ----- stderr ----- + "); + + // Test without mirrors - verify default URLs are used + uv_snapshot!(context.filters(), context.python_list() + .arg("3.10") + .arg("--show-urls") + .env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r" + success: true + exit_code: 0 + ----- stdout ----- + cpython-3.10.19-[PLATFORM] https://github.com/astral-sh/python-build-standalone/releases/download/[FILE-PATH] + pypy-3.10.16-[PLATFORM] https://downloads.python.org/pypy/[FILE-PATH] + graalpy-3.10.0-[PLATFORM] https://github.com/oracle/graalpython/releases/download/[FILE-PATH] + + ----- stderr ----- + "); +}