Make < exclusive for non-prerelease markers (#1878)

## Summary

Even when pre-releases are "allowed", per PEP 440, `pydantic<2.0.0`
should _not_ include pre-releases. This PR modifies the specifier
translation to treat `pydantic<2.0.0` as `pydantic<2.0.0.min0`, where
`min` is an internal-only version segment that's invisible to users.

Closes https://github.com/astral-sh/uv/issues/1641.
This commit is contained in:
Charlie Marsh 2024-02-24 18:02:03 -05:00 committed by GitHub
parent 53a250714c
commit 8d706b0f2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 440 additions and 58 deletions

View file

@ -95,4 +95,15 @@ impl PreReleaseStrategy {
),
}
}
/// Returns `true` if a [`PackageName`] is allowed to have pre-release versions.
pub(crate) fn allows(&self, package: &PackageName) -> bool {
match self {
Self::Disallow => false,
Self::Allow => true,
Self::IfNecessary => false,
Self::Explicit(packages) => packages.contains(package),
Self::IfNecessaryOrExplicit(packages) => packages.contains(package),
}
}
}

View file

@ -16,7 +16,6 @@ use rustc_hash::FxHashMap;
use uv_normalize::PackageName;
use crate::candidate_selector::CandidateSelector;
use crate::prerelease_mode::PreReleaseStrategy;
use crate::python_requirement::PythonRequirement;
use crate::resolver::UnavailablePackage;
@ -346,25 +345,10 @@ impl PubGrubReportFormatter<'_> {
) -> IndexSet<PubGrubHint> {
/// Returns `true` if pre-releases were allowed for a package.
fn allowed_prerelease(package: &PubGrubPackage, selector: &CandidateSelector) -> bool {
match selector.prerelease_strategy() {
PreReleaseStrategy::Disallow => false,
PreReleaseStrategy::Allow => true,
PreReleaseStrategy::IfNecessary => false,
PreReleaseStrategy::Explicit(packages) => {
if let PubGrubPackage::Package(package, ..) = package {
packages.contains(package)
} else {
false
}
}
PreReleaseStrategy::IfNecessaryOrExplicit(packages) => {
if let PubGrubPackage::Package(package, ..) = package {
packages.contains(package)
} else {
false
}
}
}
let PubGrubPackage::Package(package, ..) = package else {
return false;
};
selector.prerelease_strategy().allows(package)
}
let mut hints = IndexSet::default();

View file

@ -38,7 +38,7 @@ impl TryFrom<&VersionSpecifier> for PubGrubSpecifier {
let [rest @ .., last, _] = specifier.version().release() else {
return Err(ResolveError::InvalidTildeEquals(specifier.clone()));
};
let upper = pep440_rs::Version::new(rest.iter().chain([&(last + 1)]))
let upper = Version::new(rest.iter().chain([&(last + 1)]))
.with_epoch(specifier.version().epoch())
.with_dev(Some(0));
let version = specifier.version().clone();
@ -46,7 +46,14 @@ impl TryFrom<&VersionSpecifier> for PubGrubSpecifier {
}
Operator::LessThan => {
let version = specifier.version().clone();
Range::strictly_lower_than(version)
if version.any_prerelease() {
Range::strictly_lower_than(version)
} else {
// Per PEP 440: "The exclusive ordered comparison <V MUST NOT allow a
// pre-release of the specified version unless the specified version is itself a
// pre-release.
Range::strictly_lower_than(version.with_min(Some(0)))
}
}
Operator::LessThanEqual => {
let version = specifier.version().clone();