|
1 |
| -use pubgrub::Range; |
2 | 1 | use std::cmp::Ordering;
|
3 | 2 | use std::collections::Bound;
|
4 | 3 | use std::ops::Deref;
|
5 | 4 |
|
| 5 | +use pubgrub::Range; |
| 6 | + |
6 | 7 | use uv_distribution_filename::WheelFilename;
|
7 | 8 | use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
8 | 9 | use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
@@ -73,24 +74,43 @@ impl RequiresPython {
|
73 | 74 | }
|
74 | 75 | })?;
|
75 | 76 |
|
76 |
| - // Extract the bounds. |
77 |
| - let (lower_bound, upper_bound) = range |
78 |
| - .bounding_range() |
79 |
| - .map(|(lower_bound, upper_bound)| { |
80 |
| - ( |
81 |
| - LowerBound(lower_bound.cloned()), |
82 |
| - UpperBound(upper_bound.cloned()), |
83 |
| - ) |
84 |
| - }) |
85 |
| - .unwrap_or((LowerBound::default(), UpperBound::default())); |
86 |
| - |
87 | 77 | // Convert back to PEP 440 specifiers.
|
88 | 78 | let specifiers = VersionSpecifiers::from_release_only_bounds(range.iter());
|
89 | 79 |
|
90 |
| - Some(Self { |
91 |
| - specifiers, |
92 |
| - range: RequiresPythonRange(lower_bound, upper_bound), |
93 |
| - }) |
| 80 | + // Extract the bounds. |
| 81 | + let range = RequiresPythonRange::from_range(&range); |
| 82 | + |
| 83 | + Some(Self { specifiers, range }) |
| 84 | + } |
| 85 | + |
| 86 | + /// Split the [`RequiresPython`] at the given version. |
| 87 | + /// |
| 88 | + /// For example, if the current requirement is `>=3.10`, and the split point is `3.11`, then |
| 89 | + /// the result will be `>=3.10 and <3.11` and `>=3.11`. |
| 90 | + pub fn split(&self, bound: Bound<Version>) -> Option<(Self, Self)> { |
| 91 | + let RequiresPythonRange(.., upper) = &self.range; |
| 92 | + |
| 93 | + let upper = Range::from_range_bounds((bound, upper.clone().into())); |
| 94 | + let lower = upper.complement(); |
| 95 | + |
| 96 | + // Intersect left and right with the existing range. |
| 97 | + let lower = lower.intersection(&Range::from(self.range.clone())); |
| 98 | + let upper = upper.intersection(&Range::from(self.range.clone())); |
| 99 | + |
| 100 | + if lower.is_empty() || upper.is_empty() { |
| 101 | + None |
| 102 | + } else { |
| 103 | + Some(( |
| 104 | + Self { |
| 105 | + specifiers: VersionSpecifiers::from_release_only_bounds(lower.iter()), |
| 106 | + range: RequiresPythonRange::from_range(&lower), |
| 107 | + }, |
| 108 | + Self { |
| 109 | + specifiers: VersionSpecifiers::from_release_only_bounds(upper.iter()), |
| 110 | + range: RequiresPythonRange::from_range(&upper), |
| 111 | + }, |
| 112 | + )) |
| 113 | + } |
94 | 114 | }
|
95 | 115 |
|
96 | 116 | /// Narrow the [`RequiresPython`] by computing the intersection with the given range.
|
@@ -489,21 +509,25 @@ impl serde::Serialize for RequiresPython {
|
489 | 509 | impl<'de> serde::Deserialize<'de> for RequiresPython {
|
490 | 510 | fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
491 | 511 | let specifiers = VersionSpecifiers::deserialize(deserializer)?;
|
492 |
| - let (lower_bound, upper_bound) = release_specifiers_to_ranges(specifiers.clone()) |
493 |
| - .bounding_range() |
494 |
| - .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) |
495 |
| - .unwrap_or((Bound::Unbounded, Bound::Unbounded)); |
496 |
| - Ok(Self { |
497 |
| - specifiers, |
498 |
| - range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)), |
499 |
| - }) |
| 512 | + let range = release_specifiers_to_ranges(specifiers.clone()); |
| 513 | + let range = RequiresPythonRange::from_range(&range); |
| 514 | + Ok(Self { specifiers, range }) |
500 | 515 | }
|
501 | 516 | }
|
502 | 517 |
|
503 | 518 | #[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
504 | 519 | pub struct RequiresPythonRange(LowerBound, UpperBound);
|
505 | 520 |
|
506 | 521 | impl RequiresPythonRange {
|
| 522 | + /// Initialize a [`RequiresPythonRange`] from a [`Range`]. |
| 523 | + pub fn from_range(range: &Range<Version>) -> Self { |
| 524 | + let (lower, upper) = range |
| 525 | + .bounding_range() |
| 526 | + .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) |
| 527 | + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); |
| 528 | + Self(LowerBound(lower), UpperBound(upper)) |
| 529 | + } |
| 530 | + |
507 | 531 | /// Initialize a [`RequiresPythonRange`] with the given bounds.
|
508 | 532 | pub fn new(lower: LowerBound, upper: UpperBound) -> Self {
|
509 | 533 | Self(lower, upper)
|
@@ -967,4 +991,68 @@ mod tests {
|
967 | 991 | assert_eq!(requires_python.is_exact_without_patch(), expected);
|
968 | 992 | }
|
969 | 993 | }
|
| 994 | + |
| 995 | + #[test] |
| 996 | + fn split_version() { |
| 997 | + // Splitting `>=3.10` on `>3.12` should result in `>=3.10, <=3.12` and `>3.12`. |
| 998 | + let version_specifiers = VersionSpecifiers::from_str(">=3.10").unwrap(); |
| 999 | + let requires_python = RequiresPython::from_specifiers(&version_specifiers); |
| 1000 | + let (lower, upper) = requires_python |
| 1001 | + .split(Bound::Excluded(Version::new([3, 12]))) |
| 1002 | + .unwrap(); |
| 1003 | + assert_eq!( |
| 1004 | + lower, |
| 1005 | + RequiresPython::from_specifiers( |
| 1006 | + &VersionSpecifiers::from_str(">=3.10, <=3.12").unwrap() |
| 1007 | + ) |
| 1008 | + ); |
| 1009 | + assert_eq!( |
| 1010 | + upper, |
| 1011 | + RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">3.12").unwrap()) |
| 1012 | + ); |
| 1013 | + |
| 1014 | + // Splitting `>=3.10` on `>=3.12` should result in `>=3.10, <3.12` and `>=3.12`. |
| 1015 | + let version_specifiers = VersionSpecifiers::from_str(">=3.10").unwrap(); |
| 1016 | + let requires_python = RequiresPython::from_specifiers(&version_specifiers); |
| 1017 | + let (lower, upper) = requires_python |
| 1018 | + .split(Bound::Included(Version::new([3, 12]))) |
| 1019 | + .unwrap(); |
| 1020 | + assert_eq!( |
| 1021 | + lower, |
| 1022 | + RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.10, <3.12").unwrap()) |
| 1023 | + ); |
| 1024 | + assert_eq!( |
| 1025 | + upper, |
| 1026 | + RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap()) |
| 1027 | + ); |
| 1028 | + |
| 1029 | + // Splitting `>=3.10` on `>=3.9` should return `None`. |
| 1030 | + let version_specifiers = VersionSpecifiers::from_str(">=3.10").unwrap(); |
| 1031 | + let requires_python = RequiresPython::from_specifiers(&version_specifiers); |
| 1032 | + assert!(requires_python |
| 1033 | + .split(Bound::Included(Version::new([3, 9]))) |
| 1034 | + .is_none()); |
| 1035 | + |
| 1036 | + // Splitting `>=3.10` on `>=3.10` should return `None`. |
| 1037 | + let version_specifiers = VersionSpecifiers::from_str(">=3.10").unwrap(); |
| 1038 | + let requires_python = RequiresPython::from_specifiers(&version_specifiers); |
| 1039 | + assert!(requires_python |
| 1040 | + .split(Bound::Included(Version::new([3, 10]))) |
| 1041 | + .is_none()); |
| 1042 | + |
| 1043 | + // Splitting `>=3.9, <3.13` on `>=3.11` should result in `>=3.9, <3.11` and `>=3.11, <3.13`. |
| 1044 | + let version_specifiers = VersionSpecifiers::from_str(">=3.9, <3.13").unwrap(); |
| 1045 | + let requires_python = RequiresPython::from_specifiers(&version_specifiers); |
| 1046 | + let (lower, upper) = requires_python |
| 1047 | + .split(Bound::Included(Version::new([3, 11]))) |
| 1048 | + .unwrap(); |
| 1049 | + assert_eq!( |
| 1050 | + lower, |
| 1051 | + RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.9, <3.11").unwrap()) |
| 1052 | + ); |
| 1053 | + assert_eq!( |
| 1054 | + upper, |
| 1055 | + RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.11, <3.13").unwrap()) |
| 1056 | + ); |
| 1057 | + } |
970 | 1058 | }
|
0 commit comments