From 3d82281b5d49cc52a1415f73cc08ee023090d08d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Oct 2024 13:41:12 -0500 Subject: [PATCH] Add support for reading PEP 735 dependency groups --- crates/uv-workspace/src/pyproject.rs | 31 ++++++++++++++++++++++++ crates/uv-workspace/src/pyproject_mut.rs | 18 +++++++++++++- crates/uv-workspace/src/workspace.rs | 27 ++++++++++++++------- crates/uv/src/commands/project/add.rs | 6 +++++ crates/uv/src/commands/project/remove.rs | 6 +++++ crates/uv/src/settings.rs | 4 +-- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 9420934cf07b..d219ff8fc4c4 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -43,6 +43,8 @@ pub struct PyProjectToml { pub project: Option, /// Tool-specific metadata. pub tool: Option, + /// Non-project dependency groups, as defined in PEP 735. + pub dependency_groups: Option>>, /// The raw unserialized document. #[serde(skip)] pub raw: String, @@ -1053,6 +1055,8 @@ pub enum DependencyType { Dev, /// A dependency in `project.optional-dependencies.{0}`. Optional(ExtraName), + /// A dependency in `project.dependency-groups.{0}`. + Group(ExtraName), } /// @@ -1081,3 +1085,30 @@ mod serde_from_and_to_string { .map_err(de::Error::custom) } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use uv_pep508::ExtraName; + + use crate::pyproject::PyProjectToml; + + #[test] + fn test_read_dependency_groups() { + let toml = r#" +[dependency-groups] +test = ["a"] +"#; + + let result = + PyProjectToml::from_string(toml.to_string()).expect("Deserialization should succeed"); + let groups = result + .dependency_groups + .expect("`dependency-groups` should be present"); + let test = groups + .get(&ExtraName::from_str("test").unwrap()) + .expect("Group `test` should be present"); + assert_eq!(test, &["a".to_string()]); + } +} diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 75de5e001a4a..2c39b5626ce5 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -495,6 +495,22 @@ impl PyProjectTomlMut { } } + // Check `tool.uv.dev-dependencies`. + if let Some(groups) = self.doc.get("dependency-groups").and_then(Item::as_table) { + for (group, dependencies) in groups { + let Some(dependencies) = dependencies.as_array() else { + continue; + }; + let Ok(group) = ExtraName::new(group.to_string()) else { + continue; + }; + + if !find_dependencies(name, marker, dependencies).is_empty() { + types.push(DependencyType::Group(group)); + } + } + } + // Check `tool.uv.dev-dependencies`. if let Some(dev_dependencies) = self .doc @@ -502,7 +518,7 @@ impl PyProjectTomlMut { .and_then(Item::as_table) .and_then(|tool| tool.get("uv")) .and_then(Item::as_table) - .and_then(|tool| tool.get("dev-dependencies")) + .and_then(|uv| uv.get("dev-dependencies")) .and_then(Item::as_array) { if !find_dependencies(name, marker, dev_dependencies).is_empty() { diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index d74d345ea3ae..01003daaf52a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1603,7 +1603,8 @@ mod tests { ], "optional-dependencies": null }, - "tool": null + "tool": null, + "dependency-groups": null } } } @@ -1654,7 +1655,8 @@ mod tests { ], "optional-dependencies": null }, - "tool": null + "tool": null, + "dependency-groups": null } } } @@ -1761,7 +1763,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } @@ -1848,7 +1851,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } @@ -1898,7 +1902,8 @@ mod tests { ], "optional-dependencies": null }, - "tool": null + "tool": null, + "dependency-groups": null } } } @@ -2047,7 +2052,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } @@ -2146,7 +2152,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } @@ -2258,7 +2265,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } @@ -2344,7 +2352,8 @@ mod tests { "override-dependencies": null, "constraint-dependencies": null } - } + }, + "dependency-groups": null } } } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 43f57d48d0de..994f9005cbb1 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -202,6 +202,7 @@ pub(crate) async fn add( bail!("Project is missing a `[project]` table; add a `[project]` table to use optional dependencies, or run `{}` instead", "uv add --dev".green()) } DependencyType::Dev => (), + DependencyType::Group(_) => (), } } @@ -449,6 +450,7 @@ pub(crate) async fn add( DependencyType::Optional(ref group) => { toml.add_optional_dependency(group, &requirement, source.as_ref())? } + DependencyType::Group(_) => todo!("adding dependencies to groups is not yet supported"), }; // If the edit was inserted before the end of the list, update the existing edits. @@ -707,6 +709,9 @@ async fn lock_and_sync( DependencyType::Optional(ref group) => { toml.set_optional_dependency_minimum_version(group, *index, minimum)?; } + DependencyType::Group(_) => { + todo!("adding dependencies to groups is not yet supported") + } } modified = true; @@ -767,6 +772,7 @@ async fn lock_and_sync( let dev = DevMode::Exclude; (extras, dev) } + DependencyType::Group(_) => todo!("adding dependencies to groups is not yet supported"), }; project::sync::do_sync( diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index fd0155c7727d..eca8c3ee2c9d 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -120,6 +120,9 @@ pub(crate) async fn remove( ); } } + DependencyType::Group(_) => { + todo!("removing dependencies from groups is not yet supported") + } } } @@ -246,6 +249,9 @@ fn warn_if_present(name: &PackageName, pyproject: &PyProjectTomlMut) { "`{name}` is an optional dependency; try calling `uv remove --optional {group}`", ); } + DependencyType::Group(_) => { + // TODO(zanieb): Once we support `remove --group`, add a warning here. + } } } } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 9f2474293b50..bc961c2b2756 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -838,8 +838,8 @@ impl AddSettings { python, } = args; - let dependency_type = if let Some(group) = optional { - DependencyType::Optional(group) + let dependency_type = if let Some(extra) = optional { + DependencyType::Optional(extra) } else if dev { DependencyType::Dev } else {