Skip to content

Commit

Permalink
Respect release-only semantics of python_full_version when constructi…
Browse files Browse the repository at this point in the history
…ng markers (astral-sh#6171)

## Summary

In the resolver, we use release-only semantics to normalize
`python_full_version`. So, if we see `python_full_version < '3.13'`, we
treat that as `(Unbounded, Exclude(3.13))`. `3.13b0` evaluates as `true`
to that range, so we were accepting pre-releases for these markers.

Instead, we need to exclude pre-release segments when performing these
evaluations.

Closes astral-sh#6169.

## Test Plan

Hard to write a test for this because you need a pre-release Python
locally... so:

`echo "sqlalchemy==2.0.32" | cargo run pip compile - --python 3.13 -n`
  • Loading branch information
charliermarsh committed Aug 17, 2024
1 parent ad8e3a2 commit 5ac0b98
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 8 deletions.
9 changes: 9 additions & 0 deletions crates/pep508-rs/src/marker/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,15 @@ pub struct StringVersion {
pub version: Version,
}

impl From<Version> for StringVersion {
fn from(version: Version) -> Self {
Self {
string: version.to_string(),
version,
}
}
}

impl FromStr for StringVersion {
type Err = VersionParseError;

Expand Down
2 changes: 1 addition & 1 deletion crates/uv-dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
.index_strategy(self.index_strategy)
.build(),
&python_requirement,
ResolverMarkers::SpecificEnvironment(markers.clone()),
ResolverMarkers::specific_environment(markers.clone()),
Some(tags),
self.flat_index,
self.index,
Expand Down
48 changes: 46 additions & 2 deletions crates/uv-resolver/src/resolver/resolver_markers.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::fmt::{Display, Formatter};
use tracing::debug;

use pep508_rs::{MarkerEnvironment, MarkerTree};

#[derive(Debug, Clone)]
/// Whether we're solving for a specific environment, universally or for a specific fork.
pub enum ResolverMarkers {
/// We're solving for this specific environment only.
SpecificEnvironment(MarkerEnvironment),
SpecificEnvironment(ResolverMarkerEnvironment),
/// We're doing a universal resolution for all environments (a python version
/// constraint is expressed separately).
Universal {
Expand All @@ -20,7 +21,7 @@ pub enum ResolverMarkers {
impl ResolverMarkers {
/// Set the resolver to perform a resolution for a specific environment.
pub fn specific_environment(markers: MarkerEnvironment) -> Self {
Self::SpecificEnvironment(markers)
Self::SpecificEnvironment(ResolverMarkerEnvironment::from(markers))
}

/// Set the resolver to perform a universal resolution.
Expand Down Expand Up @@ -70,3 +71,46 @@ impl Display for ResolverMarkers {
}
}
}

/// A wrapper type around [`MarkerEnvironment`] that ensures the Python version markers are
/// release-only, to match the resolver's semantics.
#[derive(Debug, Clone)]
pub struct ResolverMarkerEnvironment(MarkerEnvironment);

impl From<MarkerEnvironment> for ResolverMarkerEnvironment {
fn from(value: MarkerEnvironment) -> Self {
// Strip `python_version`.
let python_version = value.python_version().only_release();
let value = if python_version == **value.python_version() {
value
} else {
debug!(
"Stripping pre-release from `python_version`: {}",
value.python_version()
);
value.with_python_version(python_version)
};

// Strip `python_full_version`.
let python_full_version = value.python_full_version().only_release();
let value = if python_full_version == **value.python_full_version() {
value
} else {
debug!(
"Stripping pre-release from `python_full_version`: {}",
value.python_full_version()
);
value.with_python_full_version(python_full_version)
};

Self(value)
}
}

impl std::ops::Deref for ResolverMarkerEnvironment {
type Target = MarkerEnvironment;

fn deref(&self) -> &Self::Target {
&self.0
}
}
2 changes: 1 addition & 1 deletion crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ pub(crate) async fn pip_compile(
resolution_environment(python_version, python_platform, &interpreter)?;
(
Some(tags),
ResolverMarkers::SpecificEnvironment((*markers).clone()),
ResolverMarkers::specific_environment((*markers).clone()),
)
};

Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ pub(crate) async fn pip_install(
&reinstall,
&upgrade,
Some(&tags),
ResolverMarkers::SpecificEnvironment((*markers).clone()),
ResolverMarkers::specific_environment((*markers).clone()),
python_requirement,
&client,
&flat_index,
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ pub(crate) async fn pip_sync(
&reinstall,
&upgrade,
Some(&tags),
ResolverMarkers::SpecificEnvironment((*markers).clone()),
ResolverMarkers::specific_environment((*markers).clone()),
python_requirement,
&client,
&flat_index,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ pub(crate) async fn resolve_environment<'a>(
&reinstall,
&upgrade,
Some(tags),
ResolverMarkers::SpecificEnvironment(markers.clone()),
ResolverMarkers::specific_environment(markers.clone()),
python_requirement,
&client,
&flat_index,
Expand Down Expand Up @@ -949,7 +949,7 @@ pub(crate) async fn update_environment(
reinstall,
upgrade,
Some(tags),
ResolverMarkers::SpecificEnvironment(markers.clone()),
ResolverMarkers::specific_environment(markers.clone()),
python_requirement,
&client,
&flat_index,
Expand Down

0 comments on commit 5ac0b98

Please sign in to comment.