Skip to content

Commit

Permalink
REdo requires python
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Aug 30, 2024
1 parent 0510842 commit 5470b5b
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 290 deletions.
2 changes: 1 addition & 1 deletion crates/bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ mod resolver {
let python_requirement = if universal {
PythonRequirement::from_requires_python(
interpreter,
&RequiresPython::greater_than_equal_version(&Version::new([3, 11])),
RequiresPython::greater_than_equal_version(&Version::new([3, 11])),
)
} else {
PythonRequirement::from_interpreter(interpreter)
Expand Down
23 changes: 9 additions & 14 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct Lock {
/// The list of supported environments specified by the user.
supported_environments: Vec<MarkerTree>,
/// The range of supported Python versions.
requires_python: Option<RequiresPython>,
requires_python: RequiresPython,
/// We discard the lockfile if these options don't match.
options: ResolverOptions,
/// The actual locked version and their metadata.
Expand Down Expand Up @@ -186,7 +186,7 @@ impl Lock {
fn new(
version: u32,
mut packages: Vec<Package>,
requires_python: Option<RequiresPython>,
requires_python: RequiresPython,
options: ResolverOptions,
manifest: ResolverManifest,
supported_environments: Vec<MarkerTree>,
Expand Down Expand Up @@ -241,11 +241,9 @@ impl Lock {

// Remove wheels that don't match `requires-python` and can't be selected for
// installation.
if let Some(requires_python) = &requires_python {
package
.wheels
.retain(|wheel| requires_python.matches_wheel_tag(&wheel.filename));
}
package
.wheels
.retain(|wheel| requires_python.matches_wheel_tag(&wheel.filename));
}
packages.sort_by(|dist1, dist2| dist1.id.cmp(&dist2.id));

Expand Down Expand Up @@ -390,8 +388,8 @@ impl Lock {
}

/// Returns the supported Python version range for the lockfile, if present.
pub fn requires_python(&self) -> Option<&RequiresPython> {
self.requires_python.as_ref()
pub fn requires_python(&self) -> &RequiresPython {
&self.requires_python
}

/// Returns the resolution mode used to generate this lock.
Expand Down Expand Up @@ -527,9 +525,7 @@ impl Lock {
let mut doc = toml_edit::DocumentMut::new();
doc.insert("version", value(i64::from(self.version)));

if let Some(ref requires_python) = self.requires_python {
doc.insert("requires-python", value(requires_python.to_string()));
}
doc.insert("requires-python", value(self.requires_python.to_string()));

if !self.fork_markers.is_empty() {
let fork_markers = each_element_on_its_line_array(
Expand Down Expand Up @@ -1158,8 +1154,7 @@ impl ResolverManifest {
#[serde(rename_all = "kebab-case")]
struct LockWire {
version: u32,
#[serde(default)]
requires_python: Option<RequiresPython>,
requires_python: RequiresPython,
/// If this lockfile was built from a forking resolution with non-identical forks, store the
/// forks in the lockfile so we can recreate them in subsequent resolutions.
#[serde(rename = "resolution-markers", default)]
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/pubgrub/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl PubGrubPackage {
}
}

#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
pub enum PubGrubPython {
/// The Python version installed in the current environment.
Installed,
Expand Down
74 changes: 50 additions & 24 deletions crates/uv-resolver/src/pubgrub/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::candidate_selector::CandidateSelector;
use crate::error::ErrorTree;
use crate::fork_urls::ForkUrls;
use crate::prerelease::AllowPrerelease;
use crate::python_requirement::{PythonRequirement, PythonTarget};
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
use crate::{RequiresPython, ResolverMarkers};

Expand Down Expand Up @@ -52,25 +52,19 @@ impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
&**package,
PubGrubPackageInner::Python(PubGrubPython::Target)
) {
return if let Some(target) = self.python_requirement.target() {
format!(
"the requested {package} version ({target}) does not satisfy {}",
self.compatible_range(package, set)
)
} else {
format!(
"the requested {package} version does not satisfy {}",
self.compatible_range(package, set)
)
};
let target = self.python_requirement.target();
return format!(
"the requested {package} version ({target}) does not satisfy {}",
self.compatible_range(package, set)
);
}
if matches!(
&**package,
PubGrubPackageInner::Python(PubGrubPython::Installed)
) {
let installed = self.python_requirement.exact();
return format!(
"the current {package} version ({}) does not satisfy {}",
self.python_requirement.installed(),
"the current {package} version ({installed}) does not satisfy {}",
self.compatible_range(package, set)
);
}
Expand Down Expand Up @@ -554,16 +548,13 @@ impl PubGrubReportFormatter<'_> {
&**dependency,
PubGrubPackageInner::Python(PubGrubPython::Target)
) {
if let Some(PythonTarget::RequiresPython(requires_python)) =
self.python_requirement.target()
{
hints.insert(PubGrubHint::RequiresPython {
requires_python: requires_python.clone(),
package: package.clone(),
package_set: self.simplify_set(package_set, package).into_owned(),
package_requires_python: dependency_set.clone(),
});
}
hints.insert(PubGrubHint::RequiresPython {
source: self.python_requirement.source(),
requires_python: self.python_requirement.target().clone(),
package: package.clone(),
package_set: self.simplify_set(package_set, package).into_owned(),
package_requires_python: dependency_set.clone(),
});
}
}
DerivationTree::External(External::NotRoot(..)) => {}
Expand Down Expand Up @@ -798,6 +789,7 @@ pub(crate) enum PubGrubHint {
},
/// The `Requires-Python` requirement was not satisfied.
RequiresPython {
source: PythonRequirementSource,
requires_python: RequiresPython,
#[derivative(PartialEq = "ignore", Hash = "ignore")]
package: PubGrubPackage,
Expand Down Expand Up @@ -932,6 +924,7 @@ impl std::fmt::Display for PubGrubHint {
)
}
Self::RequiresPython {
source: PythonRequirementSource::RequiresPython,
requires_python,
package,
package_set,
Expand All @@ -948,6 +941,39 @@ impl std::fmt::Display for PubGrubHint {
package_requires_python.bold(),
)
}
Self::RequiresPython {
source: PythonRequirementSource::PythonVersion,
requires_python,
package,
package_set,
package_requires_python,
} => {
write!(
f,
"{}{} The `--python-version` value ({}) includes Python versions that are not supported by your dependencies (e.g., {} only supports {}). Consider using a higher `--python-version` value.",
"hint".bold().cyan(),
":".bold(),
requires_python.bold(),
PackageRange::compatibility(package, package_set, None).bold(),
package_requires_python.bold(),
)
}
Self::RequiresPython {
source: PythonRequirementSource::Interpreter,
requires_python: _,
package,
package_set,
package_requires_python,
} => {
write!(
f,
"{}{} The Python interpreter uses a Python version that is not supported by your dependencies (e.g., {} only supports {}). Consider passing a `--python-version` value to raise the minimum supported version.",
"hint".bold().cyan(),
":".bold(),
PackageRange::compatibility(package, package_set, None).bold(),
package_requires_python.bold(),
)
}
}
}
}
Expand Down
108 changes: 48 additions & 60 deletions crates/uv-resolver/src/python_requirement.rs
Original file line number Diff line number Diff line change
@@ -1,114 +1,102 @@
use pep440_rs::{Version, VersionSpecifiers};
use pep440_rs::Version;
use uv_python::{Interpreter, PythonVersion};

use crate::{RequiresPython, RequiresPythonRange};

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PythonRequirement {
source: PythonRequirementSource,
/// The exact installed version of Python.
exact: Version,
/// The installed version of Python.
installed: Version,
installed: RequiresPython,
/// 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.
///
/// If `None`, the target version is the same as the installed version.
target: Option<PythonTarget>,
target: RequiresPython,
}

impl PythonRequirement {
/// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
/// [`PythonVersion`].
pub fn from_python_version(interpreter: &Interpreter, python_version: &PythonVersion) -> Self {
let exact = interpreter.python_full_version().version.clone();
let installed = interpreter.python_full_version().version.only_release();
let target = python_version.python_full_version().only_release();
Self {
installed: interpreter.python_full_version().version.only_release(),
target: Some(PythonTarget::Version(
python_version.python_full_version().only_release(),
)),
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: RequiresPython::greater_than_equal_version(&target),
source: PythonRequirementSource::PythonVersion,
}
}

/// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
/// [`MarkerEnvironment`].
pub fn from_requires_python(
interpreter: &Interpreter,
requires_python: &RequiresPython,
requires_python: RequiresPython,
) -> Self {
let exact = interpreter.python_full_version().version.clone();
let installed = interpreter.python_full_version().version.only_release();
Self {
installed: interpreter.python_full_version().version.only_release(),
target: Some(PythonTarget::RequiresPython(requires_python.clone())),
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: requires_python,
source: PythonRequirementSource::RequiresPython,
}
}

/// Create a [`PythonRequirement`] to resolve against an [`Interpreter`].
pub fn from_interpreter(interpreter: &Interpreter) -> Self {
let exact = interpreter.python_full_version().version.clone();
let installed = interpreter.python_full_version().version.only_release();
Self {
installed: interpreter.python_full_version().version.only_release(),
target: None,
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: RequiresPython::greater_than_equal_version(&installed),
source: PythonRequirementSource::Interpreter,
}
}

/// Narrow the [`PythonRequirement`] to the given version, if it's stricter (i.e., greater)
/// than the current `Requires-Python` minimum.
pub fn narrow(&self, target: &RequiresPythonRange) -> Option<Self> {
let Some(PythonTarget::RequiresPython(requires_python)) = self.target.as_ref() else {
return None;
};
let requires_python = requires_python.narrow(target)?;
Some(Self {
exact: self.exact.clone(),
installed: self.installed.clone(),
target: Some(PythonTarget::RequiresPython(requires_python)),
target: self.target.narrow(target)?,
source: self.source,
})
}

/// Return the exact version of Python.
pub fn exact(&self) -> &Version {
&self.exact
}

/// Return the installed version of Python.
pub fn installed(&self) -> &Version {
pub fn installed(&self) -> &RequiresPython {
&self.installed
}

/// Return the target version of Python.
pub fn target(&self) -> Option<&PythonTarget> {
self.target.as_ref()
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum PythonTarget {
/// The [`PythonTarget`] specifier is a single version specifier, as provided via
/// `--python-version` on the command line.
///
/// The use of a separate enum variant allows us to use a verbatim representation when reporting
/// back to the user.
Version(Version),
/// The [`PythonTarget`] specifier is a set of version specifiers, as extracted from the
/// `Requires-Python` field in a `pyproject.toml` or `METADATA` file.
RequiresPython(RequiresPython),
}

impl PythonTarget {
/// Returns `true` if the target Python is compatible with the [`VersionSpecifiers`].
pub fn is_compatible_with(&self, target: &VersionSpecifiers) -> bool {
match self {
PythonTarget::Version(version) => target.contains(version),
PythonTarget::RequiresPython(requires_python) => {
requires_python.is_contained_by(target)
}
}
pub fn target(&self) -> &RequiresPython {
&self.target
}

/// Returns the [`RequiresPython`] for the [`PythonTarget`] specifier.
pub fn as_requires_python(&self) -> Option<&RequiresPython> {
match self {
PythonTarget::Version(_) => None,
PythonTarget::RequiresPython(requires_python) => Some(requires_python),
}
/// Return the source of the [`PythonRequirement`].
pub fn source(&self) -> PythonRequirementSource {
self.source
}
}

impl std::fmt::Display for PythonTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PythonTarget::Version(specifier) => std::fmt::Display::fmt(specifier, f),
PythonTarget::RequiresPython(specifiers) => std::fmt::Display::fmt(specifiers, f),
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
pub enum PythonRequirementSource {
/// `--python-version`
PythonVersion,
/// `Requires-Python`
RequiresPython,
/// The discovered Python interpreter.
Interpreter,
}
Loading

0 comments on commit 5470b5b

Please sign in to comment.