mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Error when direct URL requirements don't match Requires-Python
(#2196)
## Summary Closes https://github.com/astral-sh/uv/issues/2195. ## Test Plan `cargo test`
This commit is contained in:
parent
044a77cfd2
commit
d29645ce75
4 changed files with 177 additions and 29 deletions
|
@ -17,7 +17,6 @@ use tokio_stream::wrappers::ReceiverStream;
|
|||
use tracing::{debug, info_span, instrument, trace, Instrument};
|
||||
use url::Url;
|
||||
|
||||
use distribution_filename::WheelFilename;
|
||||
use distribution_types::{
|
||||
BuiltDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource, IncompatibleWheel,
|
||||
Name, RemoteSource, SourceDist, VersionOrUrl,
|
||||
|
@ -583,38 +582,57 @@ 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;
|
||||
|
||||
// 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::IncompatibleDist(IncompatibleDist::Source(
|
||||
IncompatibleSource::RequiresPython(requires_python.clone()),
|
||||
)),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(ResolverVersion::Available(version.clone())));
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} 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)
|
||||
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::IncompatibleDist(IncompatibleDist::Source(
|
||||
IncompatibleSource::RequiresPython(requires_python.clone()),
|
||||
)),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||
}
|
||||
|
||||
PubGrubPackage::Package(package_name, extra, None) => {
|
||||
|
|
|
@ -5104,3 +5104,45 @@ fn no_stream() -> Result<()> {
|
|||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -2722,3 +2722,41 @@ fn dry_run_install_then_upgrade() -> std::result::Result<(), Box<dyn std::error:
|
|||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -3024,3 +3024,53 @@ fn no_stream() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Raise an error when a direct URL dependency's `Requires-Python` constraint is not met.
|
||||
///
|
||||
/// TODO(charlie): This currently passes, but should fail. `sync` does not currently validate the
|
||||
/// `Requires-Python` constraint for direct URL dependencies. (It _does_ respect `Requires-Python`
|
||||
/// for registry-based dependencies.)
|
||||
#[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(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue