mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00

## Summary There are a few ideas at play here: 1. pip always strips versions to the release when evaluating against a `Requires-Python`, so we now do the same. That means, e.g., using `3.13.0b0` will be accepted by a project with `Requires-Python: >= 3.13`, which does _not_ adhere to PEP 440 semantics but is somewhat intuitive. 2. Because we know we'll only be evaluating against release-only versions, we can use different semantics in PubGrub that let us collapse ranges. For example, `python_version >= '3.10' or python_version < '3.10'` can be collapsed to the truthy marker. Closes https://github.com/astral-sh/uv/issues/4714. Closes https://github.com/astral-sh/uv/issues/4272. Closes https://github.com/astral-sh/uv/issues/4719.
465 lines
18 KiB
Rust
465 lines
18 KiB
Rust
//! DO NOT EDIT
|
||
//!
|
||
//! Generated with `./scripts/sync_scenarios.sh`
|
||
//! Scenarios from <https://github.com/astral-sh/packse/tree/0.3.29/scenarios>
|
||
//!
|
||
#![cfg(all(feature = "python", feature = "pypi", unix))]
|
||
|
||
use std::env;
|
||
use std::process::Command;
|
||
|
||
use anyhow::Result;
|
||
use assert_cmd::assert::OutputAssertExt;
|
||
use assert_fs::fixture::{FileWriteStr, PathChild};
|
||
use predicates::prelude::predicate;
|
||
|
||
use common::{get_bin, python_path_with_versions, uv_snapshot, TestContext};
|
||
|
||
mod common;
|
||
|
||
/// Provision python binaries and return a `pip compile` command with options shared across all scenarios.
|
||
fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
||
let python_path = python_path_with_versions(&context.temp_dir, python_versions)
|
||
.expect("Failed to create Python test path");
|
||
let mut command = Command::new(get_bin());
|
||
command
|
||
.arg("pip")
|
||
.arg("compile")
|
||
.arg("requirements.in")
|
||
.arg("--index-url")
|
||
.arg("https://astral-sh.github.io/packse/0.3.29/simple-html/")
|
||
.arg("--find-links")
|
||
.arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.29/vendor/links.html");
|
||
context.add_shared_args(&mut command);
|
||
command.env_remove("UV_EXCLUDE_NEWER");
|
||
command.env("UV_TEST_PYTHON_PATH", python_path);
|
||
|
||
command
|
||
}
|
||
|
||
/// The user requires a package which requires a Python version greater than the
|
||
/// current version, but they use an alternative Python version for package
|
||
/// resolution.
|
||
///
|
||
/// ```text
|
||
/// incompatible-python-compatible-override
|
||
/// ├── environment
|
||
/// │ └── python3.9
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.10 (incompatible with environment)
|
||
/// ```
|
||
#[test]
|
||
fn incompatible_python_compatible_override() -> Result<()> {
|
||
let context = TestContext::new("3.9");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((r"incompatible-python-compatible-override-", "package-"));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in.write_str("incompatible-python-compatible-override-a==1.0.0")?;
|
||
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.11")
|
||
, @r###"
|
||
success: true
|
||
exit_code: 0
|
||
----- stdout -----
|
||
# This file was autogenerated by uv via the following command:
|
||
# uv pip compile requirements.in --cache-dir [CACHE_DIR] --python-version=3.11
|
||
package-a==1.0.0
|
||
# via -r requirements.in
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
|
||
Resolved 1 package in [TIME]
|
||
"###
|
||
);
|
||
|
||
output.assert().success().stdout(predicate::str::contains(
|
||
"incompatible-python-compatible-override-a==1.0.0",
|
||
));
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a compatible Python version, but they
|
||
/// request an incompatible Python version for package resolution.
|
||
///
|
||
/// ```text
|
||
/// compatible-python-incompatible-override
|
||
/// ├── environment
|
||
/// │ └── python3.11
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.10
|
||
/// ```
|
||
#[test]
|
||
fn compatible_python_incompatible_override() -> Result<()> {
|
||
let context = TestContext::new("3.11");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((r"compatible-python-incompatible-override-", "package-"));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in.write_str("compatible-python-incompatible-override-a==1.0.0")?;
|
||
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.9")
|
||
, @r###"
|
||
success: false
|
||
exit_code: 1
|
||
----- stdout -----
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.9 is not available; 3.11.[X] will be used to build dependencies instead.
|
||
× No solution found when resolving dependencies:
|
||
╰─▶ Because the requested Python version (3.9.0) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
|
||
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||
"###
|
||
);
|
||
|
||
output.assert().failure();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a incompatible Python version, but
|
||
/// they request a compatible Python version for package resolution. There are only
|
||
/// source distributions available for the package.
|
||
///
|
||
/// ```text
|
||
/// incompatible-python-compatible-override-unavailable-no-wheels
|
||
/// ├── environment
|
||
/// │ └── python3.9
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.10 (incompatible with environment)
|
||
/// ```
|
||
#[test]
|
||
fn incompatible_python_compatible_override_unavailable_no_wheels() -> Result<()> {
|
||
let context = TestContext::new("3.9");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((
|
||
r"incompatible-python-compatible-override-unavailable-no-wheels-",
|
||
"package-",
|
||
));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in
|
||
.write_str("incompatible-python-compatible-override-unavailable-no-wheels-a==1.0.0")?;
|
||
|
||
// Since there are no wheels for the package and it is not compatible with the
|
||
// local installation, we cannot build the source distribution to determine its
|
||
// dependencies.
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.11")
|
||
, @r###"
|
||
success: false
|
||
exit_code: 1
|
||
----- stdout -----
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
|
||
× No solution found when resolving dependencies:
|
||
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
|
||
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||
"###
|
||
);
|
||
|
||
output.assert().failure();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a incompatible Python version, but
|
||
/// they request a compatible Python version for package resolution. There are only
|
||
/// source distributions available for the package. The user has a compatible Python
|
||
/// version installed elsewhere on their system.
|
||
///
|
||
/// ```text
|
||
/// incompatible-python-compatible-override-available-no-wheels
|
||
/// ├── environment
|
||
/// │ ├── python3.11
|
||
/// │ └── python3.9 (active)
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.10 (incompatible with environment)
|
||
/// ```
|
||
#[test]
|
||
fn incompatible_python_compatible_override_available_no_wheels() -> Result<()> {
|
||
let context = TestContext::new("3.9");
|
||
let python_versions = &["3.11"];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((
|
||
r"incompatible-python-compatible-override-available-no-wheels-",
|
||
"package-",
|
||
));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in
|
||
.write_str("incompatible-python-compatible-override-available-no-wheels-a==1.0.0")?;
|
||
|
||
// Since there is a compatible Python version available on the system, it should be
|
||
// used to build the source distributions.
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.11")
|
||
, @r###"
|
||
success: true
|
||
exit_code: 0
|
||
----- stdout -----
|
||
# This file was autogenerated by uv via the following command:
|
||
# uv pip compile requirements.in --cache-dir [CACHE_DIR] --python-version=3.11
|
||
package-a==1.0.0
|
||
# via -r requirements.in
|
||
|
||
----- stderr -----
|
||
Resolved 1 package in [TIME]
|
||
"###
|
||
);
|
||
|
||
output.assert().success().stdout(predicate::str::contains(
|
||
"incompatible-python-compatible-override-available-no-wheels-a==1.0.0",
|
||
));
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a incompatible Python version, but
|
||
/// they request a compatible Python version for package resolution. There is a
|
||
/// wheel available for the package, but it does not have a compatible tag.
|
||
///
|
||
/// ```text
|
||
/// incompatible-python-compatible-override-no-compatible-wheels
|
||
/// ├── environment
|
||
/// │ └── python3.9
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.10 (incompatible with environment)
|
||
/// ```
|
||
#[test]
|
||
fn incompatible_python_compatible_override_no_compatible_wheels() -> Result<()> {
|
||
let context = TestContext::new("3.9");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((
|
||
r"incompatible-python-compatible-override-no-compatible-wheels-",
|
||
"package-",
|
||
));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in
|
||
.write_str("incompatible-python-compatible-override-no-compatible-wheels-a==1.0.0")?;
|
||
|
||
// Since there are no compatible wheels for the package and it is not compatible
|
||
// with the local installation, we cannot build the source distribution to
|
||
// determine its dependencies.
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.11")
|
||
, @r###"
|
||
success: false
|
||
exit_code: 1
|
||
----- stdout -----
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
|
||
× No solution found when resolving dependencies:
|
||
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
|
||
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||
"###
|
||
);
|
||
|
||
output.assert().failure();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a incompatible Python version, but
|
||
/// they request a compatible Python version for package resolution. There are only
|
||
/// source distributions available for the compatible version of the package, but
|
||
/// there is an incompatible version with a wheel available.
|
||
///
|
||
/// ```text
|
||
/// incompatible-python-compatible-override-other-wheel
|
||
/// ├── environment
|
||
/// │ └── python3.9
|
||
/// ├── root
|
||
/// │ └── requires a
|
||
/// │ ├── satisfied by a-1.0.0
|
||
/// │ └── satisfied by a-2.0.0
|
||
/// └── a
|
||
/// ├── a-1.0.0
|
||
/// │ └── requires python>=3.10 (incompatible with environment)
|
||
/// └── a-2.0.0
|
||
/// └── requires python>=3.12 (incompatible with environment)
|
||
/// ```
|
||
#[test]
|
||
fn incompatible_python_compatible_override_other_wheel() -> Result<()> {
|
||
let context = TestContext::new("3.9");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((
|
||
r"incompatible-python-compatible-override-other-wheel-",
|
||
"package-",
|
||
));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in.write_str("incompatible-python-compatible-override-other-wheel-a")?;
|
||
|
||
// Since there are no wheels for the version of the package compatible with the
|
||
// target and it is not compatible with the local installation, we cannot build the
|
||
// source distribution to determine its dependencies. The other version has wheels
|
||
// available, but is not compatible with the target version and cannot be used.
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.11")
|
||
, @r###"
|
||
success: false
|
||
exit_code: 1
|
||
----- stdout -----
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.11 is not available; 3.9.[X] will be used to build dependencies instead.
|
||
× No solution found when resolving dependencies:
|
||
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
|
||
And because only the following versions of package-a are available:
|
||
package-a==1.0.0
|
||
package-a==2.0.0
|
||
we can conclude that package-a<2.0.0 cannot be used. (1)
|
||
|
||
Because the requested Python version (3.11.0) does not satisfy Python>=3.12 and package-a==2.0.0 depends on Python>=3.12, we can conclude that package-a==2.0.0 cannot be used.
|
||
And because we know from (1) that package-a<2.0.0 cannot be used, we can conclude that all versions of package-a cannot be used.
|
||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||
"###
|
||
);
|
||
|
||
output.assert().failure();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a Python version with a patch version
|
||
/// and the user provides a target version without a patch version.
|
||
///
|
||
/// ```text
|
||
/// python-patch-override-no-patch
|
||
/// ├── environment
|
||
/// │ └── python3.8.18
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.8.4
|
||
/// ```
|
||
#[cfg(feature = "python-patch")]
|
||
#[test]
|
||
fn python_patch_override_no_patch() -> Result<()> {
|
||
let context = TestContext::new("3.8.18");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((r"python-patch-override-no-patch-", "package-"));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in.write_str("python-patch-override-no-patch-a==1.0.0")?;
|
||
|
||
// Since the resolver is asked to solve with 3.8, the minimum compatible Python
|
||
// requirement is treated as 3.8.0.
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.8")
|
||
, @r###"
|
||
success: false
|
||
exit_code: 1
|
||
----- stdout -----
|
||
|
||
----- stderr -----
|
||
× No solution found when resolving dependencies:
|
||
╰─▶ Because the requested Python version (3.8.0) does not satisfy Python>=3.8.4 and package-a==1.0.0 depends on Python>=3.8.4, we can conclude that package-a==1.0.0 cannot be used.
|
||
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||
"###
|
||
);
|
||
|
||
output.assert().failure();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// The user requires a package which requires a Python version with a patch version
|
||
/// and the user provides a target version with a compatible patch version.
|
||
///
|
||
/// ```text
|
||
/// python-patch-override-patch-compatible
|
||
/// ├── environment
|
||
/// │ └── python3.8.18
|
||
/// ├── root
|
||
/// │ └── requires a==1.0.0
|
||
/// │ └── satisfied by a-1.0.0
|
||
/// └── a
|
||
/// └── a-1.0.0
|
||
/// └── requires python>=3.8.0
|
||
/// ```
|
||
#[cfg(feature = "python-patch")]
|
||
#[test]
|
||
fn python_patch_override_patch_compatible() -> Result<()> {
|
||
let context = TestContext::new("3.8.18");
|
||
let python_versions = &[];
|
||
|
||
// In addition to the standard filters, swap out package names for shorter messages
|
||
let mut filters = context.filters();
|
||
filters.push((r"python-patch-override-patch-compatible-", "package-"));
|
||
|
||
let requirements_in = context.temp_dir.child("requirements.in");
|
||
requirements_in.write_str("python-patch-override-patch-compatible-a==1.0.0")?;
|
||
|
||
let output = uv_snapshot!(filters, command(&context, python_versions)
|
||
.arg("--python-version=3.8.0")
|
||
, @r###"
|
||
success: true
|
||
exit_code: 0
|
||
----- stdout -----
|
||
# This file was autogenerated by uv via the following command:
|
||
# uv pip compile requirements.in --cache-dir [CACHE_DIR] --python-version=3.8.0
|
||
package-a==1.0.0
|
||
# via -r requirements.in
|
||
|
||
----- stderr -----
|
||
warning: The requested Python version 3.8.0 is not available; 3.8.18 will be used to build dependencies instead.
|
||
Resolved 1 package in [TIME]
|
||
"###
|
||
);
|
||
|
||
output.assert().success().stdout(predicate::str::contains(
|
||
"python-patch-override-patch-compatible-a==1.0.0",
|
||
));
|
||
|
||
Ok(())
|
||
}
|