mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
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:
parent
53a250714c
commit
8d706b0f2a
10 changed files with 440 additions and 58 deletions
|
@ -385,6 +385,19 @@ impl Version {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the min-release part of this version, if it exists.
|
||||
///
|
||||
/// The "min" component is internal-only, and does not exist in PEP 440.
|
||||
/// The version `1.0min0` is smaller than all other `1.0` versions,
|
||||
/// like `1.0a1`, `1.0dev0`, etc.
|
||||
#[inline]
|
||||
pub fn min(&self) -> Option<u64> {
|
||||
match *self.inner {
|
||||
VersionInner::Small { ref small } => small.min(),
|
||||
VersionInner::Full { ref full } => full.min,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the release numbers and return the updated version.
|
||||
///
|
||||
/// Usually one can just use `Version::new` to create a new version with
|
||||
|
@ -512,6 +525,22 @@ impl Version {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the min-release component and return the updated version.
|
||||
///
|
||||
/// The "min" component is internal-only, and does not exist in PEP 440.
|
||||
/// The version `1.0min0` is smaller than all other `1.0` versions,
|
||||
/// like `1.0a1`, `1.0dev0`, etc.
|
||||
#[inline]
|
||||
pub fn with_min(mut self, value: Option<u64>) -> Version {
|
||||
if let VersionInner::Small { ref mut small } = Arc::make_mut(&mut self.inner) {
|
||||
if small.set_min(value) {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
self.make_full().min = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert this version to a "full" representation in-place and return a
|
||||
/// mutable borrow to the full type.
|
||||
fn make_full(&mut self) -> &mut VersionFull {
|
||||
|
@ -519,6 +548,7 @@ impl Version {
|
|||
let full = VersionFull {
|
||||
epoch: small.epoch(),
|
||||
release: small.release().to_vec(),
|
||||
min: small.min(),
|
||||
pre: small.pre(),
|
||||
post: small.post(),
|
||||
dev: small.dev(),
|
||||
|
@ -744,10 +774,13 @@ impl FromStr for Version {
|
|||
/// * Bytes 5, 4 and 3 correspond to the second, third and fourth release
|
||||
/// segments, respectively.
|
||||
/// * Bytes 2, 1 and 0 represent *one* of the following:
|
||||
/// `.devN, aN, bN, rcN, <no suffix>, .postN`. Its representation is thus:
|
||||
/// `min, .devN, aN, bN, rcN, <no suffix>, .postN`.
|
||||
/// Its representation is thus:
|
||||
/// * The most significant 3 bits of Byte 2 corresponds to a value in
|
||||
/// the range 0-5 inclusive, corresponding to dev, pre-a, pre-b, pre-rc,
|
||||
/// no-suffix or post releases, respectively.
|
||||
/// the range 0-6 inclusive, corresponding to min, dev, pre-a, pre-b, pre-rc,
|
||||
/// no-suffix or post releases, respectively. `min` is a special version that
|
||||
/// does not exist in PEP 440, but is used here to represent the smallest
|
||||
/// possible version, preceding any `dev`, `pre`, `post` or releases.
|
||||
/// * The low 5 bits combined with the bits in bytes 1 and 0 correspond
|
||||
/// to the release number of the suffix, if one exists. If there is no
|
||||
/// suffix, then this bits are always 0.
|
||||
|
@ -810,18 +843,19 @@ struct VersionSmall {
|
|||
}
|
||||
|
||||
impl VersionSmall {
|
||||
const SUFFIX_DEV: u64 = 0;
|
||||
const SUFFIX_PRE_ALPHA: u64 = 1;
|
||||
const SUFFIX_PRE_BETA: u64 = 2;
|
||||
const SUFFIX_PRE_RC: u64 = 3;
|
||||
const SUFFIX_NONE: u64 = 4;
|
||||
const SUFFIX_POST: u64 = 5;
|
||||
const SUFFIX_MIN: u64 = 0;
|
||||
const SUFFIX_DEV: u64 = 1;
|
||||
const SUFFIX_PRE_ALPHA: u64 = 2;
|
||||
const SUFFIX_PRE_BETA: u64 = 3;
|
||||
const SUFFIX_PRE_RC: u64 = 4;
|
||||
const SUFFIX_NONE: u64 = 5;
|
||||
const SUFFIX_POST: u64 = 6;
|
||||
const SUFFIX_MAX_VERSION: u64 = 0x1FFFFF;
|
||||
|
||||
#[inline]
|
||||
fn new() -> VersionSmall {
|
||||
VersionSmall {
|
||||
repr: 0x00000000_00800000,
|
||||
repr: 0x00000000_00A00000,
|
||||
release: [0, 0, 0, 0],
|
||||
len: 0,
|
||||
}
|
||||
|
@ -888,7 +922,7 @@ impl VersionSmall {
|
|||
|
||||
#[inline]
|
||||
fn set_post(&mut self, value: Option<u64>) -> bool {
|
||||
if self.pre().is_some() || self.dev().is_some() {
|
||||
if self.min().is_some() || self.pre().is_some() || self.dev().is_some() {
|
||||
return value.is_none();
|
||||
}
|
||||
match value {
|
||||
|
@ -931,7 +965,7 @@ impl VersionSmall {
|
|||
|
||||
#[inline]
|
||||
fn set_pre(&mut self, value: Option<PreRelease>) -> bool {
|
||||
if self.dev().is_some() || self.post().is_some() {
|
||||
if self.min().is_some() || self.dev().is_some() || self.post().is_some() {
|
||||
return value.is_none();
|
||||
}
|
||||
match value {
|
||||
|
@ -970,7 +1004,7 @@ impl VersionSmall {
|
|||
|
||||
#[inline]
|
||||
fn set_dev(&mut self, value: Option<u64>) -> bool {
|
||||
if self.pre().is_some() || self.post().is_some() {
|
||||
if self.min().is_some() || self.pre().is_some() || self.post().is_some() {
|
||||
return value.is_none();
|
||||
}
|
||||
match value {
|
||||
|
@ -988,6 +1022,35 @@ impl VersionSmall {
|
|||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn min(&self) -> Option<u64> {
|
||||
if self.suffix_kind() == VersionSmall::SUFFIX_MIN {
|
||||
Some(self.suffix_version())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_min(&mut self, value: Option<u64>) -> bool {
|
||||
if self.dev().is_some() || self.pre().is_some() || self.post().is_some() {
|
||||
return value.is_none();
|
||||
}
|
||||
match value {
|
||||
None => {
|
||||
self.set_suffix_kind(VersionSmall::SUFFIX_NONE);
|
||||
}
|
||||
Some(number) => {
|
||||
if number > VersionSmall::SUFFIX_MAX_VERSION {
|
||||
return false;
|
||||
}
|
||||
self.set_suffix_kind(VersionSmall::SUFFIX_MIN);
|
||||
self.set_suffix_version(number);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn local(&self) -> &[LocalSegment] {
|
||||
// A "small" version is never used if the version has a non-zero number
|
||||
|
@ -1079,6 +1142,10 @@ struct VersionFull {
|
|||
/// > Local version labels have no specific semantics assigned, but
|
||||
/// > some syntactic restrictions are imposed.
|
||||
local: Vec<LocalSegment>,
|
||||
/// An internal-only segment that does not exist in PEP 440, used to
|
||||
/// represent the smallest possible version of a release, preceding any
|
||||
/// `dev`, `pre`, `post` or releases.
|
||||
min: Option<u64>,
|
||||
}
|
||||
|
||||
/// A version number pattern.
|
||||
|
@ -1410,7 +1477,7 @@ impl<'a> Parser<'a> {
|
|||
| (u64::from(release[1]) << 40)
|
||||
| (u64::from(release[2]) << 32)
|
||||
| (u64::from(release[3]) << 24)
|
||||
| (0x80 << 16)
|
||||
| (0xA0 << 16)
|
||||
| (0x00 << 8)
|
||||
| (0x00 << 0),
|
||||
release: [
|
||||
|
@ -2243,9 +2310,9 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
|
|||
/// According to [a summary of permitted suffixes and relative
|
||||
/// ordering][pep440-suffix-ordering] the order of pre/post-releases is: .devN,
|
||||
/// aN, bN, rcN, <no suffix (final)>, .postN but also, you can have dev/post
|
||||
/// releases on beta releases, so we make a three stage ordering: ({dev: 0, a:
|
||||
/// 1, b: 2, rc: 3, (): 4, post: 5}, <preN>, <postN or None as smallest>, <devN
|
||||
/// or Max as largest>, <local>)
|
||||
/// releases on beta releases, so we make a three stage ordering: ({min: 0,
|
||||
/// dev: 1, a: 2, b: 3, rc: 4, (): 5, post: 6}, <preN>, <postN or None as
|
||||
/// smallest>, <devN or Max as largest>, <local>)
|
||||
///
|
||||
/// For post, any number is better than none (so None defaults to None<0),
|
||||
/// but for dev, no number is better (so None default to the maximum). For
|
||||
|
@ -2254,9 +2321,11 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
|
|||
///
|
||||
/// [pep440-suffix-ordering]: https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering
|
||||
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegment]) {
|
||||
match (version.pre(), version.post(), version.dev()) {
|
||||
match (version.pre(), version.post(), version.dev(), version.min()) {
|
||||
// min release
|
||||
(_pre, post, _dev, Some(n)) => (0, 0, post, n, version.local()),
|
||||
// dev release
|
||||
(None, None, Some(n)) => (0, 0, None, n, version.local()),
|
||||
(None, None, Some(n), None) => (1, 0, None, n, version.local()),
|
||||
// alpha release
|
||||
(
|
||||
Some(PreRelease {
|
||||
|
@ -2265,7 +2334,8 @@ fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegm
|
|||
}),
|
||||
post,
|
||||
dev,
|
||||
) => (1, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
None,
|
||||
) => (2, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
// beta release
|
||||
(
|
||||
Some(PreRelease {
|
||||
|
@ -2274,7 +2344,8 @@ fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegm
|
|||
}),
|
||||
post,
|
||||
dev,
|
||||
) => (2, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
None,
|
||||
) => (3, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
// alpha release
|
||||
(
|
||||
Some(PreRelease {
|
||||
|
@ -2283,11 +2354,14 @@ fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegm
|
|||
}),
|
||||
post,
|
||||
dev,
|
||||
) => (3, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
None,
|
||||
) => (4, n, post, dev.unwrap_or(u64::MAX), version.local()),
|
||||
// final release
|
||||
(None, None, None) => (4, 0, None, 0, version.local()),
|
||||
(None, None, None, None) => (5, 0, None, 0, version.local()),
|
||||
// post release
|
||||
(None, Some(post), dev) => (5, 0, Some(post), dev.unwrap_or(u64::MAX), version.local()),
|
||||
(None, Some(post), dev, None) => {
|
||||
(6, 0, Some(post), dev.unwrap_or(u64::MAX), version.local())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3367,6 +3441,9 @@ mod tests {
|
|||
])
|
||||
);
|
||||
assert_eq!(p(" \n5\n \t"), Version::new([5]));
|
||||
|
||||
// min tests
|
||||
assert!(Parser::new("1.min0".as_bytes()).parse().is_err())
|
||||
}
|
||||
|
||||
// Tests the error cases of our version parser.
|
||||
|
@ -3510,6 +3587,46 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_version() {
|
||||
// Ensure that the `.min` suffix precedes all other suffixes.
|
||||
let less = Version::new([1, 0]).with_min(Some(0));
|
||||
|
||||
let versions = &[
|
||||
"1.dev0",
|
||||
"1.0.dev456",
|
||||
"1.0a1",
|
||||
"1.0a2.dev456",
|
||||
"1.0a12.dev456",
|
||||
"1.0a12",
|
||||
"1.0b1.dev456",
|
||||
"1.0b2",
|
||||
"1.0b2.post345.dev456",
|
||||
"1.0b2.post345",
|
||||
"1.0rc1.dev456",
|
||||
"1.0rc1",
|
||||
"1.0",
|
||||
"1.0+abc.5",
|
||||
"1.0+abc.7",
|
||||
"1.0+5",
|
||||
"1.0.post456.dev34",
|
||||
"1.0.post456",
|
||||
"1.0.15",
|
||||
"1.1.dev1",
|
||||
];
|
||||
|
||||
for greater in versions.iter() {
|
||||
let greater = greater.parse::<Version>().unwrap();
|
||||
assert_eq!(
|
||||
less.cmp(&greater),
|
||||
Ordering::Less,
|
||||
"less: {:?}\ngreater: {:?}",
|
||||
less.as_bloated_debug(),
|
||||
greater.as_bloated_debug()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests our bespoke u64 decimal integer parser.
|
||||
#[test]
|
||||
fn parse_number_u64() {
|
||||
|
@ -3577,6 +3694,7 @@ mod tests {
|
|||
.field("post", &self.0.post())
|
||||
.field("dev", &self.0.dev())
|
||||
.field("local", &self.0.local())
|
||||
.field("min", &self.0.min())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -521,7 +521,7 @@ impl CacheBucket {
|
|||
CacheBucket::FlatIndex => "flat-index-v0",
|
||||
CacheBucket::Git => "git-v0",
|
||||
CacheBucket::Interpreter => "interpreter-v0",
|
||||
CacheBucket::Simple => "simple-v2",
|
||||
CacheBucket::Simple => "simple-v3",
|
||||
CacheBucket::Wheels => "wheels-v0",
|
||||
CacheBucket::Archive => "archive-v0",
|
||||
}
|
||||
|
@ -677,13 +677,13 @@ impl ArchiveTimestamp {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for ArchiveTimestamp {
|
||||
impl PartialOrd for ArchiveTimestamp {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.timestamp().cmp(&other.timestamp()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for ArchiveTimestamp {
|
||||
impl Ord for ArchiveTimestamp {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.timestamp().cmp(&other.timestamp())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -4268,3 +4268,77 @@ fn unsafe_package() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a package with a strict upper bound, allowing pre-releases. Per PEP 440, pre-releases
|
||||
/// that match the bound (e.g., `2.0.0rc1`) should be _not_ allowed.
|
||||
#[test]
|
||||
fn pre_release_upper_bound_exclude() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("flask<2.0.0")?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--prerelease=allow"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in --prerelease=allow
|
||||
click==7.1.2
|
||||
# via flask
|
||||
flask==1.1.4
|
||||
itsdangerous==1.1.0
|
||||
# via flask
|
||||
jinja2==2.11.3
|
||||
# via flask
|
||||
markupsafe==2.1.3
|
||||
# via jinja2
|
||||
werkzeug==1.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a package with a strict upper bound that includes a pre-release. Per PEP 440,
|
||||
/// pre-releases _should_ be allowed.
|
||||
#[test]
|
||||
fn pre_release_upper_bound_include() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("flask<2.0.0rc4")?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--prerelease=allow"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in --prerelease=allow
|
||||
click==8.1.7
|
||||
# via flask
|
||||
flask==2.0.0rc2
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.2
|
||||
# via flask
|
||||
markupsafe==2.1.3
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
werkzeug==3.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! DO NOT EDIT
|
||||
//!
|
||||
//! Generated with ./scripts/scenarios/update.py
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/de0bab473eeaa4445db5a8febd732c655fad3d52/scenarios>
|
||||
//! Generated with scripts/scenarios/update.py
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/4f39539c1b858e28268554604e75c69e25272e5a/scenarios>
|
||||
//!
|
||||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
|
@ -29,7 +29,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
|
|||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple")
|
||||
.arg("--find-links")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/de0bab473eeaa4445db5a8febd732c655fad3d52/vendor/links.html")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/4f39539c1b858e28268554604e75c69e25272e5a/vendor/links.html")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! DO NOT EDIT
|
||||
//!
|
||||
//! Generated with ./scripts/scenarios/update.py
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/de0bab473eeaa4445db5a8febd732c655fad3d52/scenarios>
|
||||
//! Generated with scripts/scenarios/update.py
|
||||
//! Scenarios from <https://github.com/zanieb/packse/tree/4f39539c1b858e28268554604e75c69e25272e5a/scenarios>
|
||||
//!
|
||||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn command(context: &TestContext) -> Command {
|
|||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple")
|
||||
.arg("--find-links")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/de0bab473eeaa4445db5a8febd732c655fad3d52/vendor/links.html")
|
||||
.arg("https://raw.githubusercontent.com/zanieb/packse/4f39539c1b858e28268554604e75c69e25272e5a/vendor/links.html")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
|
@ -486,7 +486,7 @@ fn dependency_excludes_range_of_compatible_versions() {
|
|||
/// There is a non-contiguous range of compatible versions for the requested package
|
||||
/// `a`, but another dependency `c` excludes the range. This is the same as
|
||||
/// `dependency-excludes-range-of-compatible-versions` but some of the versions of
|
||||
/// `a` are incompatible for another reason e.g. dependency on non-existent package
|
||||
/// `a` are incompatible for another reason e.g. dependency on non-existant package
|
||||
/// `d`.
|
||||
///
|
||||
/// ```text
|
||||
|
@ -2043,6 +2043,192 @@ fn transitive_prerelease_and_stable_dependency_many_versions_holes() {
|
|||
assert_not_installed(&context.venv, "b_041e36bc", &context.temp_dir);
|
||||
}
|
||||
|
||||
/// package-only-prereleases-boundary
|
||||
///
|
||||
/// The user requires a non-prerelease version of `a` which only has prerelease
|
||||
/// versions available. There are pre-releases on the boundary of their range.
|
||||
///
|
||||
/// ```text
|
||||
/// edcef999
|
||||
/// ├── environment
|
||||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a<0.2.0
|
||||
/// │ └── unsatisfied: no matching version
|
||||
/// └── a
|
||||
/// ├── a-0.1.0a1
|
||||
/// ├── a-0.2.0a1
|
||||
/// └── a-0.3.0a1
|
||||
/// ```
|
||||
#[test]
|
||||
fn package_only_prereleases_boundary() {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-edcef999", "albatross"));
|
||||
filters.push((r"-edcef999", ""));
|
||||
|
||||
uv_snapshot!(filters, command(&context)
|
||||
.arg("a-edcef999<0.2.0")
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ albatross==0.1.0a1
|
||||
"###);
|
||||
|
||||
// Since there are only prerelease versions of `a` available, a prerelease is
|
||||
// allowed. Since the user did not explictly request a pre-release, pre-releases at
|
||||
// the boundary should not be selected.
|
||||
assert_installed(&context.venv, "a_edcef999", "0.1.0a1", &context.temp_dir);
|
||||
}
|
||||
|
||||
/// package-prereleases-boundary
|
||||
///
|
||||
/// The user requires a non-prerelease version of `a` but has enabled pre-releases.
|
||||
/// There are pre-releases on the boundary of their range.
|
||||
///
|
||||
/// ```text
|
||||
/// 6d600873
|
||||
/// ├── environment
|
||||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a<0.2.0
|
||||
/// │ └── satisfied by a-0.1.0
|
||||
/// └── a
|
||||
/// ├── a-0.1.0
|
||||
/// ├── a-0.2.0a1
|
||||
/// └── a-0.3.0
|
||||
/// ```
|
||||
#[test]
|
||||
fn package_prereleases_boundary() {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-6d600873", "albatross"));
|
||||
filters.push((r"-6d600873", ""));
|
||||
|
||||
uv_snapshot!(filters, command(&context)
|
||||
.arg("--prerelease=allow")
|
||||
.arg("a-6d600873<0.2.0")
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ albatross==0.1.0
|
||||
"###);
|
||||
|
||||
// Since the user did not use a pre-release specifier, pre-releases at the boundary
|
||||
// should not be selected even though pre-releases are allowed.
|
||||
assert_installed(&context.venv, "a_6d600873", "0.1.0", &context.temp_dir);
|
||||
}
|
||||
|
||||
/// package-prereleases-global-boundary
|
||||
///
|
||||
/// The user requires a non-prerelease version of `a` but has enabled pre-releases.
|
||||
/// There are pre-releases on the boundary of their range.
|
||||
///
|
||||
/// ```text
|
||||
/// cf1b8081
|
||||
/// ├── environment
|
||||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a<0.2.0
|
||||
/// │ └── satisfied by a-0.1.0
|
||||
/// └── a
|
||||
/// ├── a-0.1.0
|
||||
/// ├── a-0.2.0a1
|
||||
/// └── a-0.3.0
|
||||
/// ```
|
||||
#[test]
|
||||
fn package_prereleases_global_boundary() {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-cf1b8081", "albatross"));
|
||||
filters.push((r"-cf1b8081", ""));
|
||||
|
||||
uv_snapshot!(filters, command(&context)
|
||||
.arg("--prerelease=allow")
|
||||
.arg("a-cf1b8081<0.2.0")
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ albatross==0.1.0
|
||||
"###);
|
||||
|
||||
// Since the user did not use a pre-release specifier, pre-releases at the boundary
|
||||
// should not be selected even though pre-releases are allowed.
|
||||
assert_installed(&context.venv, "a_cf1b8081", "0.1.0", &context.temp_dir);
|
||||
}
|
||||
|
||||
/// package-prereleases-specifier-boundary
|
||||
///
|
||||
/// The user requires a prerelease version of `a`. There are pre-releases on the
|
||||
/// boundary of their range.
|
||||
///
|
||||
/// ```text
|
||||
/// 357b9636
|
||||
/// ├── environment
|
||||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a<0.2.0a2
|
||||
/// │ └── satisfied by a-0.1.0
|
||||
/// └── a
|
||||
/// ├── a-0.1.0
|
||||
/// ├── a-0.2.0
|
||||
/// ├── a-0.2.0a1
|
||||
/// ├── a-0.2.0a2
|
||||
/// ├── a-0.2.0a3
|
||||
/// └── a-0.3.0
|
||||
/// ```
|
||||
#[test]
|
||||
fn package_prereleases_specifier_boundary() {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
// In addition to the standard filters, swap out package names for more realistic messages
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((r"a-357b9636", "albatross"));
|
||||
filters.push((r"-357b9636", ""));
|
||||
|
||||
uv_snapshot!(filters, command(&context)
|
||||
.arg("a-357b9636<0.2.0a2")
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ albatross==0.2.0a1
|
||||
"###);
|
||||
|
||||
// Since the user used a pre-release specifier, pre-releases at the boundary should
|
||||
// be selected.
|
||||
assert_installed(&context.venv, "a_357b9636", "0.2.0a1", &context.temp_dir);
|
||||
}
|
||||
|
||||
/// requires-python-version-does-not-exist
|
||||
///
|
||||
/// The user requires a package which requires a Python version that does not exist
|
||||
|
|
2
requirements.in
Normal file
2
requirements.in
Normal file
|
@ -0,0 +1,2 @@
|
|||
apache-airflow[otel]
|
||||
opentelemetry-exporter-prometheus<0.44
|
|
@ -5,7 +5,7 @@ Generates and updates snapshot test cases from packse scenarios.
|
|||
Usage:
|
||||
|
||||
Regenerate the scenario test file:
|
||||
|
||||
|
||||
$ ./scripts/scenarios/update.py
|
||||
|
||||
Scenarios are pinned to a specific commit. Change the `PACKSE_COMMIT` constant to update them.
|
||||
|
@ -45,7 +45,7 @@ import textwrap
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
PACKSE_COMMIT = "de0bab473eeaa4445db5a8febd732c655fad3d52"
|
||||
PACKSE_COMMIT = "4f39539c1b858e28268554604e75c69e25272e5a"
|
||||
TOOL_ROOT = Path(__file__).parent
|
||||
TEMPLATES = TOOL_ROOT / "templates"
|
||||
INSTALL_TEMPLATE = TEMPLATES / "install.mustache"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue