Skip to content

Commit

Permalink
Error when direct URL requirements don't match Requires-Python
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 5, 2024
1 parent 0bc0478 commit 7060cd4
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 29 deletions.
72 changes: 43 additions & 29 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use tokio_stream::wrappers::ReceiverStream;
use tracing::{debug, info_span, instrument, trace, warn, Instrument};
use url::Url;

use distribution_filename::WheelFilename;
use distribution_types::{
BuiltDist, Dist, DistributionMetadata, IncompatibleWheel, Name, RemoteSource, SourceDist,
VersionOrUrl,
Expand Down Expand Up @@ -569,38 +568,53 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {

// If the dist is an editable, return the version from the editable metadata.
if let Some((_local, metadata)) = self.editables.get(package_name) {
let version = metadata.version.clone();
return if range.contains(&version) {
Ok(Some(ResolverVersion::Available(version)))
} else {
Ok(None)
};
}
let version = &metadata.version;

if let Ok(wheel_filename) = WheelFilename::try_from(url.raw()) {
// If the URL is that of a wheel, extract the version.
let version = wheel_filename.version;
if range.contains(&version) {
Ok(Some(ResolverVersion::Available(version)))
} else {
Ok(None)
// The version is incompatible with the requirement.
if !range.contains(version) {
return Ok(None);
}
} else {
// Otherwise, assume this is a source distribution.
let dist = PubGrubDistribution::from_url(package_name, url);
let metadata = self
.index
.distributions
.wait(&dist.package_id())
.await
.ok_or(ResolveError::Unregistered)?;
let version = &metadata.version;
if range.contains(version) {
Ok(Some(ResolverVersion::Available(version.clone())))
} else {
Ok(None)

// The version is incompatible due to its Python requirement.
if let Some(requires_python) = metadata.requires_python.as_ref() {
let target = self.python_requirement.target();
if !requires_python.contains(target) {
return Ok(Some(ResolverVersion::Unavailable(
version.clone(),
UnavailableVersion::RequiresPython(requires_python.clone()),
)));
}
}

return Ok(Some(ResolverVersion::Available(version.clone())));
}

let dist = PubGrubDistribution::from_url(package_name, url);
let metadata = self
.index
.distributions
.wait(&dist.package_id())
.await
.ok_or(ResolveError::Unregistered)?;
let version = &metadata.version;

// The version is incompatible with the requirement.
if !range.contains(version) {
return Ok(None);
}

// The version is incompatible due to its Python requirement.
if let Some(requires_python) = metadata.requires_python.as_ref() {
let target = self.python_requirement.target();
if !requires_python.contains(target) {
return Ok(Some(ResolverVersion::Unavailable(
version.clone(),
UnavailableVersion::RequiresPython(requires_python.clone()),
)));
}
}

Ok(Some(ResolverVersion::Available(version.clone())))
}

PubGrubPackage::Package(package_name, extra, None) => {
Expand Down
42 changes: 42 additions & 0 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4756,3 +4756,45 @@ requires-python = "<=3.8"

Ok(())
}

/// Raise an error when a direct URL dependency's `Requires-Python` constraint is not met.
#[test]
fn requires_python_direct_url() -> Result<()> {
let context = TestContext::new("3.12");

// Create an editable package with a `Requires-Python` constraint that is not met.
let editable_dir = TempDir::new()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = "<=3.8"
"#,
)?;

// Write to a requirements file.
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?;

uv_snapshot!(context.compile()
.arg("requirements.in"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.12.1) does not satisfy Python<=3.8
and example==0.0.0 depends on Python<=3.8, we can conclude that
example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we
can conclude that the requirements are unsatisfiable.
"###
);

Ok(())
}
38 changes: 38 additions & 0 deletions crates/uv/tests/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2281,3 +2281,41 @@ requires-python = "<=3.8"

Ok(())
}

/// Raise an error when a direct URL's `Requires-Python` constraint is not met.
#[test]
fn requires_python_direct_url() -> Result<()> {
let context = TestContext::new("3.12");

// Create an editable package with a `Requires-Python` constraint that is not met.
let editable_dir = assert_fs::TempDir::new()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = "<=3.8"
"#,
)?;

uv_snapshot!(command(&context)
.arg(format!("example @ {}", editable_dir.path().display())), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.12.1) does not satisfy Python<=3.8
and example==0.0.0 depends on Python<=3.8, we can conclude that
example==0.0.0 cannot be used.
And because only example==0.0.0 is available and you require example, we
can conclude that the requirements are unsatisfiable.
"###
);

Ok(())
}
48 changes: 48 additions & 0 deletions crates/uv/tests/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2993,3 +2993,51 @@ requires-python = "<=3.5"

Ok(())
}

/// Raise an error when a direct URL dependency's `Requires-Python` constraint is not met.
///
/// TODO(charlie): This currently passes, but should fail.
#[test]
fn requires_python_direct_url() -> Result<()> {
let context = TestContext::new("3.12");

// Create an editable package with a `Requires-Python` constraint that is not met.
let editable_dir = assert_fs::TempDir::new()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = "<=3.5"
"#,
)?;

// Write to a requirements file.
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(&format!("example @ {}", editable_dir.path().display()))?;

// In addition to the standard filters, remove the temporary directory from the snapshot.
let filters: Vec<_> = [(r"\(from file://.*\)", "(from file://[TEMP_DIR])")]
.into_iter()
.chain(INSTA_FILTERS.to_vec())
.collect();

uv_snapshot!(filters, command(&context)
.arg("requirements.in"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
+ example==0.0.0 (from file://[TEMP_DIR])
"###
);

Ok(())
}

0 comments on commit 7060cd4

Please sign in to comment.