Skip to content

Commit

Permalink
Warn for minimal bounds when using tool.uv.sources
Browse files Browse the repository at this point in the history
When using `tool.uv.sources`, we warn that requirements have a bound, i.e. at least a lower version constraint.

When using a library, the symbols you import were introduced in different versions, creating an implicit lower bound. This forces to make that bound explicit. This is crucial to prevent backtracking resolvers from selecting an ancient versions that is not compatible (or worse, doesn't build), and a performance optimization on top.

This feature is gated to `tool.uv.sources` (as it should have been for #3263/#3443) to not unnecessarily break legacy workflows. It is also helpful specifically when using a `tool.uv.sources` section that contains constraints that are not published to pypi, e.g. for workspace dependencies. We can adjust those later to e.g. not constrain workspace dependencies with `publish = false`, but i think it's the right setting to start with.
  • Loading branch information
konstin committed May 8, 2024
1 parent bd7860d commit a5ca909
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 5 deletions.
44 changes: 39 additions & 5 deletions crates/uv-requirements/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use uv_configuration::PreviewMode;
use uv_fs::Simplified;
use uv_git::GitReference;
use uv_normalize::{ExtraName, PackageName};
use uv_warnings::warn_user;

use crate::ExtrasSpecification;

Expand Down Expand Up @@ -282,6 +283,7 @@ impl Pep621Metadata {
let requirements = lower_requirements(
&project.dependencies.unwrap_or_default(),
&project.optional_dependencies.unwrap_or_default(),
&project.name,
project_dir,
&project_sources.unwrap_or_default(),
workspace_sources,
Expand Down Expand Up @@ -320,6 +322,7 @@ impl Pep621Metadata {
pub(crate) fn lower_requirements(
dependencies: &[String],
optional_dependencies: &IndexMap<ExtraName, Vec<String>>,
project_name: &PackageName,
project_dir: &Path,
project_sources: &HashMap<PackageName, Source>,
workspace_sources: &HashMap<PackageName, Source>,
Expand All @@ -333,6 +336,7 @@ pub(crate) fn lower_requirements(
let name = requirement.name.clone();
lower_requirement(
requirement,
project_name,
project_dir,
project_sources,
workspace_sources,
Expand All @@ -352,6 +356,7 @@ pub(crate) fn lower_requirements(
let name = requirement.name.clone();
lower_requirement(
requirement,
project_name,
project_dir,
project_sources,
workspace_sources,
Expand All @@ -373,6 +378,7 @@ pub(crate) fn lower_requirements(
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
pub(crate) fn lower_requirement(
requirement: pep508_rs::Requirement,
project_name: &PackageName,
project_dir: &Path,
project_sources: &HashMap<PackageName, Source>,
workspace_sources: &HashMap<PackageName, Source>,
Expand All @@ -397,7 +403,15 @@ pub(crate) fn lower_requirement(
}

let Some(source) = source else {
let has_sources = !project_sources.is_empty() || !workspace_sources.is_empty();
// Support recursive editable inclusions.
if has_sources && requirement.version_or_url.is_none() && &requirement.name != project_name
{
warn_user!(
"You did not specify a version constraint (e.g. a lower bound) for {}",
requirement.name
);
}
return Ok(Requirement::from_pep508(requirement)?);
};

Expand Down Expand Up @@ -478,10 +492,16 @@ pub(crate) fn lower_requirement(
path_source(path, project_dir, editable)?
}
Source::Registry { index } => match requirement.version_or_url {
None => RequirementSource::Registry {
specifier: VersionSpecifiers::empty(),
index: Some(index),
},
None => {
warn_user!(
"You did not specify a version constraint (e.g. a lower bound) for {}",
requirement.name
);
RequirementSource::Registry {
specifier: VersionSpecifiers::empty(),
index: Some(index),
}
}
Some(VersionOrUrl::VersionSpecifier(version)) => RequirementSource::Registry {
specifier: version,
index: Some(index),
Expand Down Expand Up @@ -632,8 +652,8 @@ mod test {
use anyhow::Context;
use indoc::indoc;
use insta::assert_snapshot;
use uv_configuration::PreviewMode;

use uv_configuration::PreviewMode;
use uv_fs::Simplified;

use crate::{ExtrasSpecification, RequirementsSpecification};
Expand Down Expand Up @@ -758,6 +778,20 @@ mod test {
"###);
}

#[test]
fn missing_constraint() {
let input = indoc! {r#"
[project]
name = "foo"
version = "0.0.0"
dependencies = [
"tqdm",
]
"#};

assert!(from_source(input, "pyproject.toml", &ExtrasSpecification::None).is_ok());
}

#[test]
fn invalid_syntax() {
let input = indoc! {r#"
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 @@ -8653,6 +8653,48 @@ fn git_source_missing_tag() -> Result<()> {
Ok(())
}

#[test]
#[cfg(feature = "git")]
fn warn_missing_constraint() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "foo"
version = "0.0.0"
dependencies = [
"tqdm",
"anyio==4.3.0",
]
[tool.uv.sources]
anyio = { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl" }
"#})?;

uv_snapshot!(context.filters(), context.compile()
.arg("--preview")
.arg("pyproject.toml"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z --preview pyproject.toml
anyio @ https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
tqdm==4.66.2
----- stderr -----
warning: You did not specify a version constraint (e.g. a lower bound) for tqdm
Resolved 4 packages in [TIME]
"###);

Ok(())
}

#[test]
fn tool_uv_sources() -> Result<()> {
let context = TestContext::new("3.12");
Expand Down

0 comments on commit a5ca909

Please sign in to comment.