mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Trim injected python_version
marker to (major, minor) (#2395)
## Summary
Per [PEP 508](https://peps.python.org/pep-0508/), `python_version` is
just major and minor:

Right now, we're using the provided version directly, so if it's, e.g.,
`-p 3.11.8`, we'll inject the wrong marker. This was causing `pandas` to
omit `numpy` when `-p 3.11.8` was provided, since its markers look like:
```
Requires-Dist: numpy<2,>=1.22.4; python_version < "3.11"
Requires-Dist: numpy<2,>=1.23.2; python_version == "3.11"
Requires-Dist: numpy<2,>=1.26.0; python_version >= "3.12"
```
Closes https://github.com/astral-sh/uv/issues/2392.
This commit is contained in:
parent
00ec99399a
commit
3799862f5d
6 changed files with 118 additions and 34 deletions
|
@ -319,7 +319,7 @@ impl FromStr for StringVersion {
|
|||
|
||||
impl Display for StringVersion {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.version.fmt(f)
|
||||
self.string.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use tracing::{debug, warn};
|
|||
use cache_key::digest;
|
||||
use install_wheel_rs::Layout;
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
use platform_host::Platform;
|
||||
use platform_tags::{Tags, TagsError};
|
||||
use pypi_types::Scheme;
|
||||
|
@ -182,6 +182,12 @@ impl Interpreter {
|
|||
&self.markers.python_full_version.version
|
||||
}
|
||||
|
||||
/// Returns the `python_full_version` marker corresponding to this Python version.
|
||||
#[inline]
|
||||
pub const fn python_full_version(&self) -> &StringVersion {
|
||||
&self.markers.python_full_version
|
||||
}
|
||||
|
||||
/// Return the major version of this Python version.
|
||||
pub fn python_major(&self) -> u8 {
|
||||
let major = self.markers.python_full_version.version.release()[0];
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
|
||||
use crate::Interpreter;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -59,18 +60,58 @@ impl PythonVersion {
|
|||
|
||||
// Ex) `implementation_version == "3.12.0"`
|
||||
if markers.implementation_name == "cpython" {
|
||||
markers.implementation_version = self.0.clone();
|
||||
let python_full_version = self.python_full_version();
|
||||
markers.implementation_version = StringVersion {
|
||||
// Retain the verbatim representation, provided by the user.
|
||||
string: self.0.to_string(),
|
||||
version: python_full_version,
|
||||
};
|
||||
}
|
||||
|
||||
// Ex) `python_full_version == "3.12.0"`
|
||||
markers.python_full_version = self.0.clone();
|
||||
let python_full_version = self.python_full_version();
|
||||
markers.python_full_version = StringVersion {
|
||||
// Retain the verbatim representation, provided by the user.
|
||||
string: self.0.to_string(),
|
||||
version: python_full_version,
|
||||
};
|
||||
|
||||
// Ex) `python_version == "3.12"`
|
||||
markers.python_version = self.0;
|
||||
let python_version = self.python_version();
|
||||
markers.python_version = StringVersion {
|
||||
string: python_version.to_string(),
|
||||
version: python_version,
|
||||
};
|
||||
|
||||
markers
|
||||
}
|
||||
|
||||
/// Return the `python_version` marker corresponding to this Python version.
|
||||
///
|
||||
/// This should include exactly a major and minor version, but no patch version.
|
||||
///
|
||||
/// Ex) `python_version == "3.12"`
|
||||
pub fn python_version(&self) -> Version {
|
||||
let major = self.release().first().copied().unwrap_or(0);
|
||||
let minor = self.release().get(1).copied().unwrap_or(0);
|
||||
Version::new([major, minor])
|
||||
}
|
||||
|
||||
/// Return the `python_full_version` marker corresponding to this Python version.
|
||||
///
|
||||
/// This should include exactly a major, minor, and patch version (even if it's zero), along
|
||||
/// with any pre-release or post-release information.
|
||||
///
|
||||
/// Ex) `python_full_version == "3.12.0b1"`
|
||||
pub fn python_full_version(&self) -> Version {
|
||||
let major = self.release().first().copied().unwrap_or(0);
|
||||
let minor = self.release().get(1).copied().unwrap_or(0);
|
||||
let patch = self.release().get(2).copied().unwrap_or(0);
|
||||
Version::new([major, minor, patch])
|
||||
.with_pre(self.0.pre())
|
||||
.with_post(self.0.post())
|
||||
}
|
||||
|
||||
/// Return the full parsed Python version.
|
||||
pub fn version(&self) -> &Version {
|
||||
&self.0.version
|
||||
|
@ -78,24 +119,12 @@ impl PythonVersion {
|
|||
|
||||
/// Return the major version of this Python version.
|
||||
pub fn major(&self) -> u8 {
|
||||
u8::try_from(self.0.release()[0]).expect("invalid major version")
|
||||
u8::try_from(self.0.release().first().copied().unwrap_or(0)).expect("invalid major version")
|
||||
}
|
||||
|
||||
/// Return the minor version of this Python version.
|
||||
pub fn minor(&self) -> u8 {
|
||||
u8::try_from(self.0.release()[1]).expect("invalid minor version")
|
||||
}
|
||||
|
||||
/// Check if this Python version is satisfied by the given interpreter.
|
||||
///
|
||||
/// If a patch version is present, we will require an exact match.
|
||||
/// Otherwise, just the major and minor version numbers need to match.
|
||||
pub fn is_satisfied_by(&self, interpreter: &Interpreter) -> bool {
|
||||
if self.patch().is_some() {
|
||||
self.version() == interpreter.python_version()
|
||||
} else {
|
||||
(self.major(), self.minor()) == interpreter.python_tuple()
|
||||
}
|
||||
u8::try_from(self.0.release().get(1).copied().unwrap_or(0)).expect("invalid minor version")
|
||||
}
|
||||
|
||||
/// Return the patch version of this Python version, if set.
|
||||
|
@ -113,4 +142,52 @@ impl PythonVersion {
|
|||
Self::from_str(format!("{}.{}", self.major(), self.minor()).as_str())
|
||||
.expect("dropping a patch should always be valid")
|
||||
}
|
||||
|
||||
/// Check if this Python version is satisfied by the given interpreter.
|
||||
///
|
||||
/// If a patch version is present, we will require an exact match.
|
||||
/// Otherwise, just the major and minor version numbers need to match.
|
||||
pub fn is_satisfied_by(&self, interpreter: &Interpreter) -> bool {
|
||||
if self.patch().is_some() {
|
||||
self.version() == interpreter.python_version()
|
||||
} else {
|
||||
(self.major(), self.minor()) == interpreter.python_tuple()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use pep440_rs::{PreRelease, PreReleaseKind, Version};
|
||||
|
||||
use crate::PythonVersion;
|
||||
|
||||
#[test]
|
||||
fn python_markers() {
|
||||
let version = PythonVersion::from_str("3.11.0").expect("valid python version");
|
||||
assert_eq!(version.python_version(), Version::new([3, 11]));
|
||||
assert_eq!(version.python_version().to_string(), "3.11");
|
||||
assert_eq!(version.python_full_version(), Version::new([3, 11, 0]));
|
||||
assert_eq!(version.python_full_version().to_string(), "3.11.0");
|
||||
|
||||
let version = PythonVersion::from_str("3.11").expect("valid python version");
|
||||
assert_eq!(version.python_version(), Version::new([3, 11]));
|
||||
assert_eq!(version.python_version().to_string(), "3.11");
|
||||
assert_eq!(version.python_full_version(), Version::new([3, 11, 0]));
|
||||
assert_eq!(version.python_full_version().to_string(), "3.11.0");
|
||||
|
||||
let version = PythonVersion::from_str("3.11.8a1").expect("valid python version");
|
||||
assert_eq!(version.python_version(), Version::new([3, 11]));
|
||||
assert_eq!(version.python_version().to_string(), "3.11");
|
||||
assert_eq!(
|
||||
version.python_full_version(),
|
||||
Version::new([3, 11, 8]).with_pre(Some(PreRelease {
|
||||
kind: PreReleaseKind::Alpha,
|
||||
number: 1
|
||||
}))
|
||||
);
|
||||
assert_eq!(version.python_full_version().to_string(), "3.11.8a1");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use indexmap::IndexMap;
|
||||
|
@ -190,13 +191,13 @@ impl NoSolutionError {
|
|||
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
||||
available_versions.insert(
|
||||
package.clone(),
|
||||
BTreeSet::from([python_requirement.installed().clone()]),
|
||||
BTreeSet::from([python_requirement.installed().deref().clone()]),
|
||||
);
|
||||
}
|
||||
PubGrubPackage::Python(PubGrubPython::Target) => {
|
||||
available_versions.insert(
|
||||
package.clone(),
|
||||
BTreeSet::from([python_requirement.target().clone()]),
|
||||
BTreeSet::from([python_requirement.target().deref().clone()]),
|
||||
);
|
||||
}
|
||||
PubGrubPackage::Package(name, ..) => {
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
use pep440_rs::Version;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
use uv_interpreter::Interpreter;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PythonRequirement {
|
||||
/// The installed version of Python.
|
||||
installed: Version,
|
||||
installed: StringVersion,
|
||||
/// The target version of Python; that is, the version of Python for which we are resolving
|
||||
/// dependencies. This is typically the same as the installed version, but may be different
|
||||
/// when specifying an alternate Python version for the resolution.
|
||||
target: Version,
|
||||
target: StringVersion,
|
||||
}
|
||||
|
||||
impl PythonRequirement {
|
||||
pub fn new(interpreter: &Interpreter, markers: &MarkerEnvironment) -> Self {
|
||||
Self {
|
||||
installed: interpreter.python_version().clone(),
|
||||
target: markers.python_full_version.version.clone(),
|
||||
installed: interpreter.python_full_version().clone(),
|
||||
target: markers.python_full_version.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the installed version of Python.
|
||||
pub fn installed(&self) -> &Version {
|
||||
pub fn installed(&self) -> &StringVersion {
|
||||
&self.installed
|
||||
}
|
||||
|
||||
/// Return the target version of Python.
|
||||
pub fn target(&self) -> &Version {
|
||||
pub fn target(&self) -> &StringVersion {
|
||||
&self.target
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -554,7 +555,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
|||
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
||||
let version = self.python_requirement.installed();
|
||||
if range.contains(version) {
|
||||
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||
Ok(Some(ResolverVersion::Available(version.deref().clone())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -563,7 +564,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
|||
PubGrubPackage::Python(PubGrubPython::Target) => {
|
||||
let version = self.python_requirement.target();
|
||||
if range.contains(version) {
|
||||
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||
Ok(Some(ResolverVersion::Available(version.deref().clone())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue