From 61fcdfb2e4c9c5a678266b1f261827b5d422a077 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 13 Feb 2025 15:44:01 -0600 Subject: [PATCH] Allow `-p` to use complex Python version requests in `uv pip compile` (#11486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #11285 Closes https://github.com/astral-sh/uv/pull/11437 This changes `-p` from an alias of `--python-version` to `--python` while retaining backwards compatibility for `--python-version`-like fallback behavior when the requested version, e.g., `-p 3.12`, cannot be found. This was initially implemented with a hidden `--python-legacy` flag which allows us to special case the short `-p` flag — unlike the implementation in #11437. However, after further discussion, we decided the behavior difference between `-p` and `--python` would be confusing so now `-p` is an alias for `--python` and `--python` is special-cased when a version is used. Additionally, we now respect the `UV_PYTHON` environment variable, but it is ignored when `--python-version` is set. If you want different `--python-version` and `--python` values, you must do so explicitly. I considered banning this, but it is valid for e.g. `--python pypy --python-version 3.12` --- crates/uv-cli/src/lib.rs | 24 +- crates/uv/src/commands/pip/compile.rs | 33 +- crates/uv/tests/it/pip_compile.rs | 479 +++++++++++++++++++++++++- crates/uv/tests/it/show_settings.rs | 112 +++--- docs/reference/cli.md | 6 +- 5 files changed, 583 insertions(+), 71 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 2faf98e3ed3d..d9e08a813e88 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1079,15 +1079,23 @@ pub struct PipCompileArgs { /// The Python interpreter to use during resolution. /// - /// A Python interpreter is required for building source distributions to - /// determine package metadata when there are not wheels. + /// A Python interpreter is required for building source distributions to determine package + /// metadata when there are not wheels. + /// + /// The interpreter is also used to determine the default minimum Python version, unless + /// `--python-version` is provided. /// - /// The interpreter is also used to determine the default minimum Python - /// version, unless `--python-version` is provided. + /// This option respects `UV_PYTHON`, but when set via environment variable, it is overridden + /// by `--python-version`. /// - /// See `uv help python` for details on Python discovery and supported - /// request formats. - #[arg(long, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string)] + /// See `uv help python` for details on Python discovery and supported request formats. + #[arg( + long, + short, + verbatim_doc_comment, + help_heading = "Python options", + value_parser = parse_maybe_string + )] pub python: Option>, /// Install packages into the system Python environment. @@ -1170,7 +1178,7 @@ pub struct PipCompileArgs { /// /// If a patch version is omitted, the minimum patch version is assumed. For /// example, `3.8` is mapped to `3.8.0`. - #[arg(long, short, help_heading = "Python options")] + #[arg(long, help_heading = "Python options")] pub python_version: Option, /// The platform for which requirements should be resolved. diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 0dc21a51273c..e16410b8ecc5 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use std::env; use std::path::Path; +use std::str::FromStr; use std::sync::Arc; use anyhow::{anyhow, Result}; @@ -86,14 +87,14 @@ pub(crate) async fn pip_compile( no_build_isolation: bool, no_build_isolation_package: Vec, build_options: BuildOptions, - python_version: Option, + mut python_version: Option, python_platform: Option, universal: bool, exclude_newer: Option, sources: SourceStrategy, annotation_style: AnnotationStyle, link_mode: LinkMode, - python: Option, + mut python: Option, system: bool, python_preference: PythonPreference, concurrency: Concurrency, @@ -103,6 +104,29 @@ pub(crate) async fn pip_compile( printer: Printer, preview: PreviewMode, ) -> Result { + // Respect `UV_PYTHON` + if python.is_none() && python_version.is_none() { + if let Ok(request) = std::env::var("UV_PYTHON") { + if !request.is_empty() { + python = Some(request); + } + } + } + + // If `--python` / `-p` is a simple Python version request, we treat it as `--python-version` + // for backwards compatibility. `-p` was previously aliased to `--python-version` but changed to + // `--python` for consistency with the rest of the CLI in v0.6.0. Since we assume metadata is + // consistent across wheels, it's okay for us to build wheels (to determine metadata) with an + // alternative Python interpreter as long as we solve with the proper Python version tags. + if python_version.is_none() { + if let Some(request) = python.as_ref() { + if let Ok(version) = PythonVersion::from_str(request) { + python_version = Some(version); + python = None; + } + } + } + // If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`), // return an error. if !extras.is_empty() && !requirements.iter().any(RequirementsSource::allows_extras) { @@ -189,8 +213,8 @@ pub(crate) async fn pip_compile( let request = PythonRequest::parse(python); PythonInstallation::find(&request, environment_preference, python_preference, &cache) } else { - // TODO(zanieb): The split here hints at a problem with the abstraction; we should be able to use - // `PythonInstallation::find(...)` here. + // TODO(zanieb): The split here hints at a problem with the request abstraction; we should + // be able to use `PythonInstallation::find(...)` here. let request = if let Some(version) = python_version.as_ref() { // TODO(zanieb): We should consolidate `VersionRequest` and `PythonVersion` PythonRequest::Version(VersionRequest::from(version)) @@ -216,6 +240,7 @@ pub(crate) async fn pip_compile( && python_version.minor() == interpreter.python_minor() }; if no_build.is_none() + && python.is_none() && python_version.version() != interpreter.python_version() && (python_version.patch().is_some() || !matches_without_patch) { diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 53836facf568..4b12871f69e4 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -1309,6 +1309,89 @@ fn compile_python_312() -> Result<()> { "### ); + // This should work with the short flag + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("-p") + .arg("3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in -p 3.12 + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // And `--python` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python") + .arg("3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python 3.12 + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // And `UV_PYTHON` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .env("UV_PYTHON", "3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + Ok(()) } @@ -1347,7 +1430,7 @@ fn compile_python_312_annotation_line() -> Result<()> { /// Compile for 3.12 when only a different interpreter version is available. #[test] fn compile_fallback_interpreter() -> Result<()> { - let context = TestContext::new("3.10"); + let context = TestContext::new("3.10").with_filtered_python_sources(); let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("black==23.10.1")?; @@ -1379,6 +1462,400 @@ fn compile_fallback_interpreter() -> Result<()> { "### ); + // This should work for the short flag too + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("-p") + .arg("3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in -p 3.12 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + warning: The requested Python version 3.12 is not available; 3.10.[X] will be used to build dependencies instead. + Resolved 6 packages in [TIME] + "### + ); + + // And for `UV_PYTHON` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .env(EnvVars::UV_PYTHON, "3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + warning: The requested Python version 3.12 is not available; 3.10.[X] will be used to build dependencies instead. + Resolved 6 packages in [TIME] + "### + ); + + // And for `--python` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python") + .arg("3.12"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python 3.12 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + warning: The requested Python version 3.12 is not available; 3.10.[X] will be used to build dependencies instead. + Resolved 6 packages in [TIME] + "### + ); + + // We also allow requesting alternative implementations, but we should fail if we can't find it + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("-p") + .arg("pypy"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for PyPy in [PYTHON SOURCES] + "### + ); + + // Similarly, we fail if we receive a range request that cannot be satisfied + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("-p") + .arg(">=3.12"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python >=3.12 in [PYTHON SOURCES] + "### + ); + + Ok(()) +} + +#[test] +fn compile_python_conflicts() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("black==23.10.1")?; + + // `-p` and `--python` cannot be used together + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python") + .arg("3.12") + .arg("-p") + .arg("3.12"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: the argument '--python ' cannot be used multiple times + + Usage: uv pip compile [OPTIONS] ... + + For more information, try '--help'. + "### + ); + + // `UV_PYTHON` should be usable with `-p` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("-p") + .arg("3.12") + .env("UV_PYTHON", "3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in -p 3.12 + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // `UV_PYTHON` should be usable with `--python` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python") + .arg("3.12") + .env("UV_PYTHON", "3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python 3.12 + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // `UV_PYTHON` should be usable with `--python-version` + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .env("UV_PYTHON", "3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-version 3.12 + black==23.10.1 + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + Ok(()) +} + +#[test] +fn compile_python_build_version_different_than_target() -> Result<()> { + let context = + TestContext::new_with_versions(&["3.12", "3.10", "3.11"]).with_filtered_python_sources(); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("black==23.10.1")?; + + // The build interpreter can differ from the target version + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .arg("-p") + .arg("3.11") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-version 3.12 -p 3.11 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .arg("-p") + .arg("cpython@3.11") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-version 3.12 -p cpython@3.11 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // If we can't find the interpreter, we fail + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .arg("-p") + .arg("pypy@3.11") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for PyPy 3.11 in [PYTHON SOURCES] + "### + ); + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .arg("-p") + .arg("3.13") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.13 in [PYTHON SOURCES] + "### + ); + + // `UV_PYTHON` is ignored if `--python-version` is set + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .env(EnvVars::UV_PYTHON, "3.11") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-version 3.12 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + // `UV_PYTHON` is ignored if `--python-version` is set + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in") + .arg("--python-version") + .arg("3.12") + .env(EnvVars::UV_PYTHON, "pypy") + .env_remove(EnvVars::VIRTUAL_ENV), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-version 3.12 + black==23.10.[X] + # via -r requirements.in + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==24.0 + # via black + pathspec==0.12.1 + # via black + platformdirs==4.2.0 + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + Ok(()) } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index cf05fc61b988..f72a0f2ba21d 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -51,7 +51,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { // Resolution should use the lowest direct version, and generate hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -215,14 +215,14 @@ fn resolve_uv_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Resolution should use the highest version, and generate hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .arg("--resolution=highest"), @r#" + .arg("--resolution=highest"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -386,7 +386,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Resolution should use the highest version, and omit hashes. @@ -394,7 +394,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { .arg("--show-settings") .arg("requirements.in") .arg("--resolution=highest") - .arg("--no-generate-hashes"), @r#" + .arg("--no-generate-hashes"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -558,7 +558,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -598,7 +598,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { // Resolution should use the lowest direct version, and generate hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -762,7 +762,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Remove the `uv.toml` file. @@ -771,7 +771,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { // Resolution should use the highest version, and omit hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -905,7 +905,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Add configuration to the `pyproject.toml` file. @@ -923,7 +923,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { // Resolution should use the lowest direct version, and generate hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -1087,7 +1087,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -1119,7 +1119,7 @@ fn resolve_index_url() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -1312,7 +1312,7 @@ fn resolve_index_url() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Providing an additional index URL on the command-line should be merged with the @@ -1321,7 +1321,7 @@ fn resolve_index_url() -> anyhow::Result<()> { .arg("--show-settings") .arg("requirements.in") .arg("--extra-index-url") - .arg("https://test.pypi.org/simple"), @r#" + .arg("https://test.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -1545,7 +1545,7 @@ fn resolve_index_url() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -1577,7 +1577,7 @@ fn resolve_find_links() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -1741,7 +1741,7 @@ fn resolve_find_links() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -1772,7 +1772,7 @@ fn resolve_top_level() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -1906,7 +1906,7 @@ fn resolve_top_level() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Write out to both the top-level (`tool.uv`) and the pip section (`tool.uv.pip`). The @@ -1930,7 +1930,7 @@ fn resolve_top_level() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2123,14 +2123,14 @@ fn resolve_top_level() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // But the command-line should take precedence over both. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .arg("--resolution=lowest-direct"), @r#" + .arg("--resolution=lowest-direct"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2323,7 +2323,7 @@ fn resolve_top_level() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -2354,7 +2354,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r#" + .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2488,7 +2488,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Add a local configuration to generate hashes. @@ -2502,7 +2502,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r#" + .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2636,7 +2636,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Add a local configuration to override the user configuration. @@ -2650,7 +2650,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r#" + .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2784,7 +2784,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // However, the user-level `tool.uv.pip` settings override the project-level `tool.uv` settings. @@ -2800,7 +2800,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r#" + .env(EnvVars::XDG_CONFIG_HOME, xdg.path()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -2934,7 +2934,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -3128,7 +3128,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { // Resolution should use the lowest direct version, and generate hashes. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3262,7 +3262,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -3304,7 +3304,7 @@ fn resolve_both() -> anyhow::Result<()> { // Resolution should succeed, but warn that the `pip` section in `pyproject.toml` is ignored. uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3469,7 +3469,7 @@ fn resolve_both() -> anyhow::Result<()> { ----- stderr ----- warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file. - "# + "### ); Ok(()) @@ -3598,7 +3598,7 @@ fn resolve_config_file() -> anyhow::Result<()> { .arg("--show-settings") .arg("--config-file") .arg(config.path()) - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -3762,7 +3762,7 @@ fn resolve_config_file() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Write in `pyproject.toml` schema. @@ -3870,7 +3870,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .current_dir(&child), @r#" + .current_dir(&child), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4004,7 +4004,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Adding a `tool.uv` section should cause us to ignore the `uv.toml`. @@ -4021,7 +4021,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") .arg("requirements.in") - .current_dir(&child), @r#" + .current_dir(&child), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4155,7 +4155,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -4180,7 +4180,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("--show-settings") - .arg("requirements.in"), @r#" + .arg("requirements.in"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4325,7 +4325,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) @@ -4353,7 +4353,7 @@ fn index_priority() -> anyhow::Result<()> { .arg("requirements.in") .arg("--show-settings") .arg("--index-url") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4548,14 +4548,14 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) .arg("requirements.in") .arg("--show-settings") .arg("--default-index") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4750,7 +4750,7 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); let config = context.temp_dir.child("uv.toml"); @@ -4763,7 +4763,7 @@ fn index_priority() -> anyhow::Result<()> { .arg("requirements.in") .arg("--show-settings") .arg("--default-index") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -4958,7 +4958,7 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Prefer the `--index` from the CLI, but treat the index from the file as the default. @@ -4966,7 +4966,7 @@ fn index_priority() -> anyhow::Result<()> { .arg("requirements.in") .arg("--show-settings") .arg("--index") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -5161,7 +5161,7 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); let config = context.temp_dir.child("uv.toml"); @@ -5176,7 +5176,7 @@ fn index_priority() -> anyhow::Result<()> { .arg("requirements.in") .arg("--show-settings") .arg("--index-url") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -5371,7 +5371,7 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); // Prefer the `--extra-index-url` from the CLI, but not as the default. @@ -5379,7 +5379,7 @@ fn index_priority() -> anyhow::Result<()> { .arg("requirements.in") .arg("--show-settings") .arg("--extra-index-url") - .arg("https://cli.pypi.org/simple"), @r#" + .arg("https://cli.pypi.org/simple"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -5574,7 +5574,7 @@ fn index_priority() -> anyhow::Result<()> { } ----- stderr ----- - "# + "### ); Ok(()) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index e4145f21eca0..7c20da7d92fb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5848,12 +5848,14 @@ uv pip compile [OPTIONS] ...

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

-
--python python

The Python interpreter to use during resolution.

+
--python, -p python

The Python interpreter to use during resolution.

A Python interpreter is required for building source distributions to determine package metadata when there are not wheels.

The interpreter is also used to determine the default minimum Python version, unless --python-version is provided.

+

This option respects UV_PYTHON, but when set via environment variable, it is overridden by --python-version.

+

See uv python for details on Python discovery and supported request formats.

--python-platform python-platform

The platform for which requirements should be resolved.

@@ -5955,7 +5957,7 @@ uv pip compile [OPTIONS] ...
  • only-system: Only use system Python installations; never use managed Python installations
  • -
    --python-version, -p python-version

    The Python version to use for resolution.

    +
    --python-version python-version

    The Python version to use for resolution.

    For example, 3.8 or 3.8.17.