mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-01 17:42:19 +00:00
Simplify pep440 -> version ranges conversion (#8683)
This commit is contained in:
parent
d0afd10ca4
commit
c1a0fb35e8
24 changed files with 286 additions and 415 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4329,6 +4329,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-warnings",
|
||||
"version-ranges",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
|
|
|
@ -31,6 +31,7 @@ spdx = { workspace = true }
|
|||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
version-ranges = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@ use std::str::FromStr;
|
|||
use tracing::debug;
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep440::{Version, VersionRangesSpecifier, VersionSpecifiers};
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pep508::{Requirement, VersionOrUrl};
|
||||
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
|
||||
use uv_warnings::warn_user_once;
|
||||
use version_ranges::Ranges;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ValidationError {
|
||||
|
@ -134,9 +135,9 @@ impl PyProjectToml {
|
|||
);
|
||||
passed = false;
|
||||
}
|
||||
VersionRangesSpecifier::from_pep440_specifiers(specifier)
|
||||
.ok()
|
||||
.and_then(|specifier| Some(specifier.bounding_range()?.1 != Bound::Unbounded))
|
||||
Ranges::from(specifier.clone())
|
||||
.bounding_range()
|
||||
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ serde = { workspace = true, features = ["derive"] }
|
|||
tracing = { workspace = true, optional = true }
|
||||
unicode-width = { workspace = true }
|
||||
unscanny = { workspace = true }
|
||||
# Adds conversions from [`VersionSpecifiers`] to [`version_ranges::Ranges`]
|
||||
version-ranges = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "version-ranges")]
|
||||
pub use version_ranges_specifier::{VersionRangesSpecifier, VersionRangesSpecifierError};
|
||||
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
|
||||
pub use {
|
||||
version::{
|
||||
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
|
||||
|
@ -42,4 +42,4 @@ mod version_specifier;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(feature = "version-ranges")]
|
||||
mod version_ranges_specifier;
|
||||
mod version_ranges;
|
||||
|
|
|
@ -32,6 +32,8 @@ pub enum Operator {
|
|||
/// `!= 1.2.*`
|
||||
NotEqualStar,
|
||||
/// `~=`
|
||||
///
|
||||
/// Invariant: With `~=`, there are always at least 2 release segments.
|
||||
TildeEqual,
|
||||
/// `<`
|
||||
LessThan,
|
||||
|
|
192
crates/uv-pep440/src/version_ranges.rs
Normal file
192
crates/uv-pep440/src/version_ranges.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
//! Convert [`VersionSpecifiers`] to [`version_ranges::Ranges`].
|
||||
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use crate::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
|
||||
|
||||
impl From<VersionSpecifiers> for Ranges<Version> {
|
||||
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
|
||||
/// semantics.
|
||||
fn from(specifiers: VersionSpecifiers) -> Self {
|
||||
let mut range = Ranges::full();
|
||||
for specifier in specifiers {
|
||||
range = range.intersection(&Self::from(specifier));
|
||||
}
|
||||
range
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionSpecifier> for Ranges<Version> {
|
||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using PEP 440
|
||||
/// semantics.
|
||||
fn from(specifier: VersionSpecifier) -> Self {
|
||||
let VersionSpecifier { operator, version } = specifier;
|
||||
match operator {
|
||||
Operator::Equal => Ranges::singleton(version),
|
||||
Operator::ExactEqual => Ranges::singleton(version),
|
||||
Operator::NotEqual => Ranges::singleton(version).complement(),
|
||||
Operator::TildeEqual => {
|
||||
let [rest @ .., last, _] = version.release() else {
|
||||
unreachable!("~= must have at least two segments");
|
||||
};
|
||||
let upper = Version::new(rest.iter().chain([&(last + 1)]))
|
||||
.with_epoch(version.epoch())
|
||||
.with_dev(Some(0));
|
||||
|
||||
Ranges::from_range_bounds(version..upper)
|
||||
}
|
||||
Operator::LessThan => {
|
||||
if version.any_prerelease() {
|
||||
Ranges::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."
|
||||
Ranges::strictly_lower_than(version.with_min(Some(0)))
|
||||
}
|
||||
}
|
||||
Operator::LessThanEqual => Ranges::lower_than(version),
|
||||
Operator::GreaterThan => {
|
||||
// Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of
|
||||
// the given version unless V itself is a post release."
|
||||
|
||||
if let Some(dev) = version.dev() {
|
||||
Ranges::higher_than(version.with_dev(Some(dev + 1)))
|
||||
} else if let Some(post) = version.post() {
|
||||
Ranges::higher_than(version.with_post(Some(post + 1)))
|
||||
} else {
|
||||
Ranges::strictly_higher_than(version.with_max(Some(0)))
|
||||
}
|
||||
}
|
||||
Operator::GreaterThanEqual => Ranges::higher_than(version),
|
||||
Operator::EqualStar => {
|
||||
let low = version.with_dev(Some(0));
|
||||
let mut high = low.clone();
|
||||
if let Some(post) = high.post() {
|
||||
high = high.with_post(Some(post + 1));
|
||||
} else if let Some(pre) = high.pre() {
|
||||
high = high.with_pre(Some(Prerelease {
|
||||
kind: pre.kind,
|
||||
number: pre.number + 1,
|
||||
}));
|
||||
} else {
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
}
|
||||
Ranges::from_range_bounds(low..high)
|
||||
}
|
||||
Operator::NotEqualStar => {
|
||||
let low = version.with_dev(Some(0));
|
||||
let mut high = low.clone();
|
||||
if let Some(post) = high.post() {
|
||||
high = high.with_post(Some(post + 1));
|
||||
} else if let Some(pre) = high.pre() {
|
||||
high = high.with_pre(Some(Prerelease {
|
||||
kind: pre.kind,
|
||||
number: pre.number + 1,
|
||||
}));
|
||||
} else {
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
}
|
||||
Ranges::from_range_bounds(low..high).complement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the [`VersionSpecifiers`] to a PubGrub-compatible version range, using release-only
|
||||
/// semantics.
|
||||
///
|
||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||
///
|
||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||
pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges<Version> {
|
||||
let mut range = Ranges::full();
|
||||
for specifier in specifiers {
|
||||
range = range.intersection(&release_specifier_to_range(specifier));
|
||||
}
|
||||
range
|
||||
}
|
||||
|
||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
||||
/// semantics.
|
||||
///
|
||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||
///
|
||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||
pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version> {
|
||||
let VersionSpecifier { operator, version } = specifier;
|
||||
match operator {
|
||||
Operator::Equal => {
|
||||
let version = version.only_release();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::ExactEqual => {
|
||||
let version = version.only_release();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::NotEqual => {
|
||||
let version = version.only_release();
|
||||
Ranges::singleton(version).complement()
|
||||
}
|
||||
Operator::TildeEqual => {
|
||||
let [rest @ .., last, _] = version.release() else {
|
||||
unreachable!("~= must have at least two segments");
|
||||
};
|
||||
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
||||
let version = version.only_release();
|
||||
Ranges::from_range_bounds(version..upper)
|
||||
}
|
||||
Operator::LessThan => {
|
||||
let version = version.only_release();
|
||||
Ranges::strictly_lower_than(version)
|
||||
}
|
||||
Operator::LessThanEqual => {
|
||||
let version = version.only_release();
|
||||
Ranges::lower_than(version)
|
||||
}
|
||||
Operator::GreaterThan => {
|
||||
let version = version.only_release();
|
||||
Ranges::strictly_higher_than(version)
|
||||
}
|
||||
Operator::GreaterThanEqual => {
|
||||
let version = version.only_release();
|
||||
Ranges::higher_than(version)
|
||||
}
|
||||
Operator::EqualStar => {
|
||||
let low = version.only_release();
|
||||
let high = {
|
||||
let mut high = low.clone();
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
high
|
||||
};
|
||||
Ranges::from_range_bounds(low..high)
|
||||
}
|
||||
Operator::NotEqualStar => {
|
||||
let low = version.only_release();
|
||||
let high = {
|
||||
let mut high = low.clone();
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
high
|
||||
};
|
||||
Ranges::from_range_bounds(low..high).complement()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Bound;
|
||||
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use crate::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
|
||||
|
||||
/// The conversion between PEP 440 [`VersionSpecifier`] and version-ranges
|
||||
/// [`VersionRangesSpecifier`] failed.
|
||||
#[derive(Debug)]
|
||||
pub enum VersionRangesSpecifierError {
|
||||
/// The `~=` operator requires at least two release segments
|
||||
InvalidTildeEquals(VersionSpecifier),
|
||||
}
|
||||
|
||||
impl Error for VersionRangesSpecifierError {}
|
||||
|
||||
impl Display for VersionRangesSpecifierError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidTildeEquals(specifier) => {
|
||||
write!(
|
||||
f,
|
||||
"The `~=` operator requires at least two release segments: `{specifier}`"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A range of versions that can be used to satisfy a requirement.
|
||||
#[derive(Debug)]
|
||||
pub struct VersionRangesSpecifier(Ranges<Version>);
|
||||
|
||||
impl VersionRangesSpecifier {
|
||||
/// Returns an iterator over the bounds of the [`VersionRangesSpecifier`].
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Bound<Version>, &Bound<Version>)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Return the bounding [`Ranges`] of the [`VersionRangesSpecifier`].
|
||||
pub fn bounding_range(&self) -> Option<(Bound<&Version>, Bound<&Version>)> {
|
||||
self.0.bounding_range()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ranges<Version>> for VersionRangesSpecifier {
|
||||
fn from(range: Ranges<Version>) -> Self {
|
||||
VersionRangesSpecifier(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionRangesSpecifier> for Ranges<Version> {
|
||||
/// Convert a PubGrub specifier to a range of versions.
|
||||
fn from(specifier: VersionRangesSpecifier) -> Self {
|
||||
specifier.0
|
||||
}
|
||||
}
|
||||
|
||||
impl VersionRangesSpecifier {
|
||||
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
|
||||
/// semantics.
|
||||
pub fn from_pep440_specifiers(
|
||||
specifiers: &VersionSpecifiers,
|
||||
) -> Result<Self, VersionRangesSpecifierError> {
|
||||
let mut range = Ranges::full();
|
||||
for specifier in specifiers.iter() {
|
||||
range = range.intersection(&Self::from_pep440_specifier(specifier)?.into());
|
||||
}
|
||||
Ok(Self(range))
|
||||
}
|
||||
|
||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using PEP 440
|
||||
/// semantics.
|
||||
pub fn from_pep440_specifier(
|
||||
specifier: &VersionSpecifier,
|
||||
) -> Result<Self, VersionRangesSpecifierError> {
|
||||
let ranges = match specifier.operator() {
|
||||
Operator::Equal => {
|
||||
let version = specifier.version().clone();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::ExactEqual => {
|
||||
let version = specifier.version().clone();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::NotEqual => {
|
||||
let version = specifier.version().clone();
|
||||
Ranges::singleton(version).complement()
|
||||
}
|
||||
Operator::TildeEqual => {
|
||||
let [rest @ .., last, _] = specifier.version().release() else {
|
||||
return Err(VersionRangesSpecifierError::InvalidTildeEquals(
|
||||
specifier.clone(),
|
||||
));
|
||||
};
|
||||
let upper = Version::new(rest.iter().chain([&(last + 1)]))
|
||||
.with_epoch(specifier.version().epoch())
|
||||
.with_dev(Some(0));
|
||||
let version = specifier.version().clone();
|
||||
Ranges::from_range_bounds(version..upper)
|
||||
}
|
||||
Operator::LessThan => {
|
||||
let version = specifier.version().clone();
|
||||
if version.any_prerelease() {
|
||||
Ranges::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."
|
||||
Ranges::strictly_lower_than(version.with_min(Some(0)))
|
||||
}
|
||||
}
|
||||
Operator::LessThanEqual => {
|
||||
let version = specifier.version().clone();
|
||||
Ranges::lower_than(version)
|
||||
}
|
||||
Operator::GreaterThan => {
|
||||
// Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of
|
||||
// the given version unless V itself is a post release."
|
||||
let version = specifier.version().clone();
|
||||
if let Some(dev) = version.dev() {
|
||||
Ranges::higher_than(version.with_dev(Some(dev + 1)))
|
||||
} else if let Some(post) = version.post() {
|
||||
Ranges::higher_than(version.with_post(Some(post + 1)))
|
||||
} else {
|
||||
Ranges::strictly_higher_than(version.with_max(Some(0)))
|
||||
}
|
||||
}
|
||||
Operator::GreaterThanEqual => {
|
||||
let version = specifier.version().clone();
|
||||
Ranges::higher_than(version)
|
||||
}
|
||||
Operator::EqualStar => {
|
||||
let low = specifier.version().clone().with_dev(Some(0));
|
||||
let mut high = low.clone();
|
||||
if let Some(post) = high.post() {
|
||||
high = high.with_post(Some(post + 1));
|
||||
} else if let Some(pre) = high.pre() {
|
||||
high = high.with_pre(Some(Prerelease {
|
||||
kind: pre.kind,
|
||||
number: pre.number + 1,
|
||||
}));
|
||||
} else {
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
}
|
||||
Ranges::from_range_bounds(low..high)
|
||||
}
|
||||
Operator::NotEqualStar => {
|
||||
let low = specifier.version().clone().with_dev(Some(0));
|
||||
let mut high = low.clone();
|
||||
if let Some(post) = high.post() {
|
||||
high = high.with_post(Some(post + 1));
|
||||
} else if let Some(pre) = high.pre() {
|
||||
high = high.with_pre(Some(Prerelease {
|
||||
kind: pre.kind,
|
||||
number: pre.number + 1,
|
||||
}));
|
||||
} else {
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
}
|
||||
Ranges::from_range_bounds(low..high).complement()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self(ranges))
|
||||
}
|
||||
|
||||
/// Convert the [`VersionSpecifiers`] to a PubGrub-compatible version range, using release-only
|
||||
/// semantics.
|
||||
///
|
||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||
///
|
||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||
pub fn from_release_specifiers(
|
||||
specifiers: &VersionSpecifiers,
|
||||
) -> Result<Self, VersionRangesSpecifierError> {
|
||||
let mut range = Ranges::full();
|
||||
for specifier in specifiers.iter() {
|
||||
range = range.intersection(&Self::from_release_specifier(specifier)?.into());
|
||||
}
|
||||
Ok(Self(range))
|
||||
}
|
||||
|
||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
||||
/// semantics.
|
||||
///
|
||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||
///
|
||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||
pub fn from_release_specifier(
|
||||
specifier: &VersionSpecifier,
|
||||
) -> Result<Self, VersionRangesSpecifierError> {
|
||||
let ranges = match specifier.operator() {
|
||||
Operator::Equal => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::ExactEqual => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::singleton(version)
|
||||
}
|
||||
Operator::NotEqual => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::singleton(version).complement()
|
||||
}
|
||||
Operator::TildeEqual => {
|
||||
let [rest @ .., last, _] = specifier.version().release() else {
|
||||
return Err(VersionRangesSpecifierError::InvalidTildeEquals(
|
||||
specifier.clone(),
|
||||
));
|
||||
};
|
||||
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::from_range_bounds(version..upper)
|
||||
}
|
||||
Operator::LessThan => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::strictly_lower_than(version)
|
||||
}
|
||||
Operator::LessThanEqual => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::lower_than(version)
|
||||
}
|
||||
Operator::GreaterThan => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::strictly_higher_than(version)
|
||||
}
|
||||
Operator::GreaterThanEqual => {
|
||||
let version = specifier.version().only_release();
|
||||
Ranges::higher_than(version)
|
||||
}
|
||||
Operator::EqualStar => {
|
||||
let low = specifier.version().only_release();
|
||||
let high = {
|
||||
let mut high = low.clone();
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
high
|
||||
};
|
||||
Ranges::from_range_bounds(low..high)
|
||||
}
|
||||
Operator::NotEqualStar => {
|
||||
let low = specifier.version().only_release();
|
||||
let high = {
|
||||
let mut high = low.clone();
|
||||
let mut release = high.release().to_vec();
|
||||
*release.last_mut().unwrap() += 1;
|
||||
high = high.with_release(release);
|
||||
high
|
||||
};
|
||||
Ranges::from_range_bounds(low..high).complement()
|
||||
}
|
||||
};
|
||||
Ok(Self(ranges))
|
||||
}
|
||||
}
|
|
@ -112,6 +112,15 @@ impl FromIterator<VersionSpecifier> for VersionSpecifiers {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for VersionSpecifiers {
|
||||
type Item = VersionSpecifier;
|
||||
type IntoIter = std::vec::IntoIter<VersionSpecifier>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionSpecifiers {
|
||||
type Err = VersionSpecifiersParseError;
|
||||
|
||||
|
|
|
@ -53,8 +53,7 @@ use std::sync::MutexGuard;
|
|||
use itertools::Either;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::sync::LazyLock;
|
||||
use uv_pep440::{Operator, VersionRangesSpecifier};
|
||||
use uv_pep440::{Version, VersionSpecifier};
|
||||
use uv_pep440::{release_specifier_to_range, Operator, Version, VersionSpecifier};
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use crate::marker::MarkerValueExtra;
|
||||
|
@ -744,11 +743,9 @@ impl Edges {
|
|||
|
||||
/// Returns the [`Edges`] for a version specifier.
|
||||
fn from_specifier(specifier: VersionSpecifier) -> Edges {
|
||||
let specifier =
|
||||
VersionRangesSpecifier::from_release_specifier(&normalize_specifier(specifier))
|
||||
.unwrap();
|
||||
let specifier = release_specifier_to_range(normalize_specifier(specifier));
|
||||
Edges::Version {
|
||||
edges: Edges::from_range(&specifier.into()),
|
||||
edges: Edges::from_range(&specifier),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -763,10 +760,8 @@ impl Edges {
|
|||
for version in versions {
|
||||
let specifier = VersionSpecifier::equals_version(version.clone());
|
||||
let specifier = python_version_to_full_version(specifier)?;
|
||||
let pubgrub_specifier =
|
||||
VersionRangesSpecifier::from_release_specifier(&normalize_specifier(specifier))
|
||||
.unwrap();
|
||||
range = range.union(&pubgrub_specifier.into());
|
||||
let pubgrub_specifier = release_specifier_to_range(normalize_specifier(specifier));
|
||||
range = range.union(&pubgrub_specifier);
|
||||
}
|
||||
|
||||
if negated {
|
||||
|
|
|
@ -19,7 +19,7 @@ use uv_distribution_types::{
|
|||
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{Version, VersionRangesSpecifierError};
|
||||
use uv_pep440::Version;
|
||||
use uv_pep508::MarkerTree;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
|
@ -37,9 +37,6 @@ pub enum ResolveError {
|
|||
#[error("Attempted to wait on an unregistered task: `{_0}`")]
|
||||
UnregisteredTask(String),
|
||||
|
||||
#[error(transparent)]
|
||||
VersionRangesSpecifier(#[from] VersionRangesSpecifierError),
|
||||
|
||||
#[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
|
||||
ConflictingOverrideUrls(PackageName, String, String),
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ pub use options::{Flexibility, Options, OptionsBuilder};
|
|||
pub use preferences::{Preference, PreferenceError, Preferences};
|
||||
pub use prerelease::PrereleaseMode;
|
||||
pub use python_requirement::PythonRequirement;
|
||||
pub use requires_python::{RequiresPython, RequiresPythonError, RequiresPythonRange};
|
||||
pub use requires_python::{RequiresPython, RequiresPythonRange};
|
||||
pub use resolution::{
|
||||
AnnotationStyle, ConflictingDistributionError, DisplayResolutionGraph, ResolutionGraph,
|
||||
};
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
use std::iter;
|
||||
|
||||
use itertools::Itertools;
|
||||
use pubgrub::Range;
|
||||
use pubgrub::Ranges;
|
||||
use tracing::warn;
|
||||
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep440::{Version, VersionRangesSpecifier, VersionSpecifiers};
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pypi_types::{
|
||||
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, Requirement,
|
||||
RequirementSource, VerbatimParsedUrl,
|
||||
};
|
||||
|
||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
||||
use crate::ResolveError;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PubGrubDependency {
|
||||
pub(crate) package: PubGrubPackage,
|
||||
pub(crate) version: Range<Version>,
|
||||
pub(crate) version: Ranges<Version>,
|
||||
|
||||
/// The original version specifiers from the requirement.
|
||||
pub(crate) specifier: Option<VersionSpecifiers>,
|
||||
|
@ -32,12 +30,12 @@ impl PubGrubDependency {
|
|||
pub(crate) fn from_requirement<'a>(
|
||||
requirement: &'a Requirement,
|
||||
source_name: Option<&'a PackageName>,
|
||||
) -> impl Iterator<Item = Result<Self, ResolveError>> + 'a {
|
||||
) -> impl Iterator<Item = Self> + 'a {
|
||||
// Add the package, plus any extra variants.
|
||||
iter::once(None)
|
||||
.chain(requirement.extras.clone().into_iter().map(Some))
|
||||
.map(|extra| PubGrubRequirement::from_requirement(requirement, extra))
|
||||
.filter_map_ok(move |requirement| {
|
||||
.filter_map(move |requirement| {
|
||||
let PubGrubRequirement {
|
||||
package,
|
||||
version,
|
||||
|
@ -87,7 +85,7 @@ impl PubGrubDependency {
|
|||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PubGrubRequirement {
|
||||
pub(crate) package: PubGrubPackage,
|
||||
pub(crate) version: Range<Version>,
|
||||
pub(crate) version: Ranges<Version>,
|
||||
pub(crate) specifier: Option<VersionSpecifiers>,
|
||||
pub(crate) url: Option<VerbatimParsedUrl>,
|
||||
}
|
||||
|
@ -95,10 +93,7 @@ pub(crate) struct PubGrubRequirement {
|
|||
impl PubGrubRequirement {
|
||||
/// Convert a [`Requirement`] to a PubGrub-compatible package and range, while returning the URL
|
||||
/// on the [`Requirement`], if any.
|
||||
pub(crate) fn from_requirement(
|
||||
requirement: &Requirement,
|
||||
extra: Option<ExtraName>,
|
||||
) -> Result<Self, ResolveError> {
|
||||
pub(crate) fn from_requirement(requirement: &Requirement, extra: Option<ExtraName>) -> Self {
|
||||
let (verbatim_url, parsed_url) = match &requirement.source {
|
||||
RequirementSource::Registry { specifier, .. } => {
|
||||
return Self::from_registry_requirement(specifier, extra, requirement);
|
||||
|
@ -159,29 +154,27 @@ impl PubGrubRequirement {
|
|||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
package: PubGrubPackage::from_package(
|
||||
requirement.name.clone(),
|
||||
extra,
|
||||
requirement.marker.clone(),
|
||||
),
|
||||
version: Range::full(),
|
||||
version: Ranges::full(),
|
||||
specifier: None,
|
||||
url: Some(VerbatimParsedUrl {
|
||||
parsed_url,
|
||||
verbatim: verbatim_url.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_registry_requirement(
|
||||
specifier: &VersionSpecifiers,
|
||||
extra: Option<ExtraName>,
|
||||
requirement: &Requirement,
|
||||
) -> Result<PubGrubRequirement, ResolveError> {
|
||||
let version = VersionRangesSpecifier::from_pep440_specifiers(specifier)?.into();
|
||||
|
||||
let requirement = Self {
|
||||
) -> PubGrubRequirement {
|
||||
Self {
|
||||
package: PubGrubPackage::from_package(
|
||||
requirement.name.clone(),
|
||||
extra,
|
||||
|
@ -189,9 +182,7 @@ impl PubGrubRequirement {
|
|||
),
|
||||
specifier: Some(specifier.clone()),
|
||||
url: None,
|
||||
version,
|
||||
};
|
||||
|
||||
Ok(requirement)
|
||||
version: Ranges::from(specifier.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
use itertools::Itertools;
|
||||
use pubgrub::Range;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::Bound;
|
||||
use std::ops::Deref;
|
||||
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_pep440::{
|
||||
Version, VersionRangesSpecifier, VersionRangesSpecifierError, VersionSpecifier,
|
||||
VersionSpecifiers,
|
||||
};
|
||||
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RequiresPythonError {
|
||||
#[error(transparent)]
|
||||
VersionRangesSpecifier(#[from] VersionRangesSpecifierError),
|
||||
}
|
||||
|
||||
/// The `Requires-Python` requirement specifier.
|
||||
///
|
||||
/// See: <https://packaging.python.org/en/latest/guides/dropping-older-python-versions/>
|
||||
|
@ -54,16 +44,15 @@ impl RequiresPython {
|
|||
}
|
||||
|
||||
/// Returns a [`RequiresPython`] from a version specifier.
|
||||
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result<Self, RequiresPythonError> {
|
||||
let (lower_bound, upper_bound) =
|
||||
VersionRangesSpecifier::from_release_specifiers(specifiers)?
|
||||
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Self {
|
||||
let (lower_bound, upper_bound) = release_specifiers_to_ranges(specifiers.clone())
|
||||
.bounding_range()
|
||||
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
||||
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
||||
Ok(Self {
|
||||
Self {
|
||||
specifiers: specifiers.clone(),
|
||||
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`RequiresPython`] to express the intersection of the given version specifiers.
|
||||
|
@ -71,23 +60,19 @@ impl RequiresPython {
|
|||
/// For example, given `>=3.8` and `>=3.9`, this would return `>=3.9`.
|
||||
pub fn intersection<'a>(
|
||||
specifiers: impl Iterator<Item = &'a VersionSpecifiers>,
|
||||
) -> Result<Option<Self>, RequiresPythonError> {
|
||||
) -> Option<Self> {
|
||||
// Convert to PubGrub range and perform an intersection.
|
||||
let range = specifiers
|
||||
.into_iter()
|
||||
.map(VersionRangesSpecifier::from_release_specifiers)
|
||||
.fold_ok(None, |range: Option<Range<Version>>, requires_python| {
|
||||
.map(|specifier| release_specifiers_to_ranges(specifier.clone()))
|
||||
.fold(None, |range: Option<Range<Version>>, requires_python| {
|
||||
if let Some(range) = range {
|
||||
Some(range.intersection(&requires_python.into()))
|
||||
Some(range.intersection(&requires_python))
|
||||
} else {
|
||||
Some(requires_python.into())
|
||||
Some(requires_python)
|
||||
}
|
||||
})?;
|
||||
|
||||
let Some(range) = range else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Extract the bounds.
|
||||
let (lower_bound, upper_bound) = range
|
||||
.bounding_range()
|
||||
|
@ -102,10 +87,10 @@ impl RequiresPython {
|
|||
// Convert back to PEP 440 specifiers.
|
||||
let specifiers = VersionSpecifiers::from_release_only_bounds(range.iter());
|
||||
|
||||
Ok(Some(Self {
|
||||
Some(Self {
|
||||
specifiers,
|
||||
range: RequiresPythonRange(lower_bound, upper_bound),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Narrow the [`RequiresPython`] by computing the intersection with the given range.
|
||||
|
@ -260,14 +245,10 @@ impl RequiresPython {
|
|||
/// N.B. This operation should primarily be used when evaluating the compatibility of a
|
||||
/// project's `Requires-Python` specifier against a dependency's `Requires-Python` specifier.
|
||||
pub fn is_contained_by(&self, target: &VersionSpecifiers) -> bool {
|
||||
let Ok(target) = VersionRangesSpecifier::from_release_specifiers(target) else {
|
||||
return false;
|
||||
};
|
||||
let target = target
|
||||
.iter()
|
||||
.next()
|
||||
.map(|(lower, _)| lower)
|
||||
.unwrap_or(&Bound::Unbounded);
|
||||
let target = release_specifiers_to_ranges(target.clone())
|
||||
.bounding_range()
|
||||
.map(|bounding_range| bounding_range.0.cloned())
|
||||
.unwrap_or(Bound::Unbounded);
|
||||
|
||||
// We want, e.g., `self.range.lower()` to be `>=3.8` and `target` to be `>=3.7`.
|
||||
//
|
||||
|
@ -508,9 +489,7 @@ impl serde::Serialize for RequiresPython {
|
|||
impl<'de> serde::Deserialize<'de> for RequiresPython {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let specifiers = VersionSpecifiers::deserialize(deserializer)?;
|
||||
let (lower_bound, upper_bound) =
|
||||
VersionRangesSpecifier::from_release_specifiers(&specifiers)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
let (lower_bound, upper_bound) = release_specifiers_to_ranges(specifiers.clone())
|
||||
.bounding_range()
|
||||
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
||||
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::RequiresPython;
|
|||
#[test]
|
||||
fn requires_python_included() {
|
||||
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &[
|
||||
"bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl",
|
||||
"black-24.4.2-cp310-cp310-win_amd64.whl",
|
||||
|
@ -30,7 +30,7 @@ fn requires_python_included() {
|
|||
}
|
||||
|
||||
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &["dearpygui-1.11.1-cp312-cp312-win_amd64.whl"];
|
||||
for wheel_name in wheel_names {
|
||||
assert!(
|
||||
|
@ -40,7 +40,7 @@ fn requires_python_included() {
|
|||
}
|
||||
|
||||
let version_specifiers = VersionSpecifiers::from_str("==3.12.6").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl"];
|
||||
for wheel_name in wheel_names {
|
||||
assert!(
|
||||
|
@ -50,7 +50,7 @@ fn requires_python_included() {
|
|||
}
|
||||
|
||||
let version_specifiers = VersionSpecifiers::from_str("==3.12").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl"];
|
||||
for wheel_name in wheel_names {
|
||||
assert!(
|
||||
|
@ -63,7 +63,7 @@ fn requires_python_included() {
|
|||
#[test]
|
||||
fn requires_python_dropped() {
|
||||
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &[
|
||||
"PySocks-1.7.1-py27-none-any.whl",
|
||||
"black-24.4.2-cp39-cp39-win_amd64.whl",
|
||||
|
@ -83,7 +83,7 @@ fn requires_python_dropped() {
|
|||
}
|
||||
|
||||
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
let wheel_names = &["dearpygui-1.11.1-cp310-cp310-win_amd64.whl"];
|
||||
for wheel_name in wheel_names {
|
||||
assert!(
|
||||
|
@ -152,7 +152,7 @@ fn is_exact_without_patch() {
|
|||
];
|
||||
for (version, expected) in test_cases {
|
||||
let version_specifiers = VersionSpecifiers::from_str(version).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||
assert_eq!(requires_python.is_exact_without_patch(), expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use dashmap::DashMap;
|
|||
use either::Either;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use itertools::Itertools;
|
||||
use pubgrub::{Incompatibility, Range, State};
|
||||
use pubgrub::{Incompatibility, Range, Ranges, State};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use tokio::sync::oneshot;
|
||||
|
@ -34,7 +34,7 @@ use uv_distribution_types::{
|
|||
};
|
||||
use uv_git::GitResolver;
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_pep440::{Version, VersionRangesSpecifier, MIN_VERSION};
|
||||
use uv_pep440::{release_specifiers_to_ranges, Version, MIN_VERSION};
|
||||
use uv_pep508::MarkerTree;
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_pypi_types::{Requirement, ResolutionMetadata, VerbatimParsedUrl};
|
||||
|
@ -465,7 +465,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
let version = match version {
|
||||
ResolverVersion::Available(version) => version,
|
||||
ResolverVersion::Unavailable(version, reason) => {
|
||||
state.add_unavailable_version(version, reason)?;
|
||||
state.add_unavailable_version(version, reason);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
@ -1241,7 +1241,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
requirements
|
||||
.iter()
|
||||
.flat_map(|requirement| PubGrubDependency::from_requirement(requirement, None))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.collect()
|
||||
}
|
||||
PubGrubPackageInner::Package {
|
||||
name,
|
||||
|
@ -1349,12 +1349,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
python_requirement,
|
||||
);
|
||||
|
||||
let mut dependencies = requirements
|
||||
let mut dependencies: Vec<_> = requirements
|
||||
.iter()
|
||||
.flat_map(|requirement| {
|
||||
PubGrubDependency::from_requirement(requirement, Some(name))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
.collect();
|
||||
|
||||
// If a package has metadata for an enabled dependency group,
|
||||
// add a dependency from it to the same package with the group
|
||||
|
@ -1610,10 +1610,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
extras: constraint.extras.clone(),
|
||||
source: constraint.source.clone(),
|
||||
origin: constraint.origin.clone(),
|
||||
marker
|
||||
marker,
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
let requires_python = python_requirement.target();
|
||||
let python_marker = python_requirement.to_marker_tree();
|
||||
|
@ -1644,7 +1643,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
extras: constraint.extras.clone(),
|
||||
source: constraint.source.clone(),
|
||||
origin: constraint.origin.clone(),
|
||||
marker
|
||||
marker,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
@ -2223,14 +2222,10 @@ impl ForkState {
|
|||
.map(|specifier| {
|
||||
Locals::map(local, specifier)
|
||||
.map_err(ResolveError::InvalidVersion)
|
||||
.and_then(|specifier| {
|
||||
Ok(VersionRangesSpecifier::from_pep440_specifier(
|
||||
&specifier,
|
||||
)?)
|
||||
})
|
||||
.map(Ranges::from)
|
||||
})
|
||||
.fold_ok(Range::full(), |range, specifier| {
|
||||
range.intersection(&specifier.into())
|
||||
range.intersection(&specifier)
|
||||
})?;
|
||||
|
||||
// Add the local version.
|
||||
|
@ -2288,11 +2283,7 @@ impl ForkState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_unavailable_version(
|
||||
&mut self,
|
||||
version: Version,
|
||||
reason: UnavailableVersion,
|
||||
) -> Result<(), ResolveError> {
|
||||
fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
|
||||
// Incompatible requires-python versions are special in that we track
|
||||
// them as incompatible dependencies instead of marking the package version
|
||||
// as unavailable directly.
|
||||
|
@ -2301,9 +2292,6 @@ impl ForkState {
|
|||
| IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
|
||||
) = reason
|
||||
{
|
||||
let python_version: Range<Version> =
|
||||
VersionRangesSpecifier::from_release_specifiers(&requires_python)?.into();
|
||||
|
||||
let package = &self.next;
|
||||
self.pubgrub
|
||||
.add_incompatibility(Incompatibility::from_dependency(
|
||||
|
@ -2314,13 +2302,13 @@ impl ForkState {
|
|||
PythonRequirementKind::Installed => PubGrubPython::Installed,
|
||||
PythonRequirementKind::Target => PubGrubPython::Target,
|
||||
})),
|
||||
python_version.clone(),
|
||||
release_specifiers_to_ranges(requires_python),
|
||||
),
|
||||
));
|
||||
self.pubgrub
|
||||
.partial_solution
|
||||
.add_decision(self.next.clone(), version);
|
||||
return Ok(());
|
||||
return;
|
||||
};
|
||||
self.pubgrub
|
||||
.add_incompatibility(Incompatibility::custom_version(
|
||||
|
@ -2328,7 +2316,6 @@ impl ForkState {
|
|||
version.clone(),
|
||||
UnavailableReason::Version(reason),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Subset the current markers with the new markers and update the python requirements fields
|
||||
|
|
|
@ -396,7 +396,7 @@ async fn build_package(
|
|||
// (3) `Requires-Python` in `pyproject.toml`
|
||||
if interpreter_request.is_none() {
|
||||
if let Ok(workspace) = workspace {
|
||||
interpreter_request = find_requires_python(workspace)?
|
||||
interpreter_request = find_requires_python(workspace)
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| {
|
||||
|
|
|
@ -771,7 +771,4 @@ pub(crate) enum Error {
|
|||
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
VersionRangesSpecifier(#[from] uv_pep440::VersionRangesSpecifierError),
|
||||
}
|
||||
|
|
|
@ -362,7 +362,7 @@ async fn init_project(
|
|||
}
|
||||
ref
|
||||
python_request @ PythonRequest::Version(VersionRequest::Range(ref specifiers, _)) => {
|
||||
let requires_python = RequiresPython::from_specifiers(specifiers)?;
|
||||
let requires_python = RequiresPython::from_specifiers(specifiers);
|
||||
|
||||
let python_request = if no_pin_python {
|
||||
None
|
||||
|
@ -417,10 +417,7 @@ async fn init_project(
|
|||
(requires_python, python_request)
|
||||
}
|
||||
}
|
||||
} else if let Some(requires_python) = workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| find_requires_python(workspace).ok().flatten())
|
||||
{
|
||||
} else if let Some(requires_python) = workspace.as_ref().and_then(find_requires_python) {
|
||||
// (2) `Requires-Python` from the workspace
|
||||
let python_request = PythonRequest::Version(VersionRequest::Range(
|
||||
requires_python.specifiers().clone(),
|
||||
|
|
|
@ -390,7 +390,7 @@ async fn do_lock(
|
|||
|
||||
// Determine the supported Python range. If no range is defined, and warn and default to the
|
||||
// current minor version.
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
let requires_python = find_requires_python(workspace);
|
||||
|
||||
let requires_python = if let Some(requires_python) = requires_python {
|
||||
if requires_python.is_unbounded() {
|
||||
|
|
|
@ -176,9 +176,6 @@ pub(crate) enum ProjectError {
|
|||
#[error(transparent)]
|
||||
Operation(#[from] pip::operations::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
||||
|
||||
#[error(transparent)]
|
||||
Interpreter(#[from] uv_python::InterpreterError),
|
||||
|
||||
|
@ -208,9 +205,7 @@ pub(crate) enum ProjectError {
|
|||
///
|
||||
/// For a [`Workspace`] with multiple packages, the `Requires-Python` bound is the union of the
|
||||
/// `Requires-Python` bounds of all the packages.
|
||||
pub(crate) fn find_requires_python(
|
||||
workspace: &Workspace,
|
||||
) -> Result<Option<RequiresPython>, uv_resolver::RequiresPythonError> {
|
||||
pub(crate) fn find_requires_python(workspace: &Workspace) -> Option<RequiresPython> {
|
||||
RequiresPython::intersection(workspace.packages().values().filter_map(|member| {
|
||||
member
|
||||
.pyproject_toml()
|
||||
|
@ -341,7 +336,7 @@ impl WorkspacePython {
|
|||
python_request: Option<PythonRequest>,
|
||||
workspace: &Workspace,
|
||||
) -> Result<Self, ProjectError> {
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
let requires_python = find_requires_python(workspace);
|
||||
|
||||
let (source, python_request) = if let Some(request) = python_request {
|
||||
// (1) Explicit request from user
|
||||
|
|
|
@ -55,7 +55,7 @@ pub(crate) async fn find(
|
|||
};
|
||||
|
||||
if let Some(project) = project {
|
||||
request = find_requires_python(project.workspace())?
|
||||
request = find_requires_python(project.workspace())
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| {
|
||||
|
|
|
@ -253,7 +253,7 @@ fn assert_pin_compatible_with_project(pin: &Pin, virtual_project: &VirtualProjec
|
|||
project_workspace.project_name(),
|
||||
project_workspace.workspace().install_path().display()
|
||||
);
|
||||
let requires_python = find_requires_python(project_workspace.workspace())?;
|
||||
let requires_python = find_requires_python(project_workspace.workspace());
|
||||
(requires_python, "project")
|
||||
}
|
||||
VirtualProject::NonProject(workspace) => {
|
||||
|
@ -261,7 +261,7 @@ fn assert_pin_compatible_with_project(pin: &Pin, virtual_project: &VirtualProjec
|
|||
"Discovered virtual workspace at: {}",
|
||||
workspace.install_path().display()
|
||||
);
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
let requires_python = find_requires_python(workspace);
|
||||
(requires_python, "workspace")
|
||||
}
|
||||
};
|
||||
|
|
|
@ -198,7 +198,6 @@ async fn venv_impl(
|
|||
if interpreter_request.is_none() {
|
||||
if let Some(project) = project {
|
||||
interpreter_request = find_requires_python(project.workspace())
|
||||
.into_diagnostic()?
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue