Skip to content

Commit

Permalink
Add support for reading PEP 735 dependency groups
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Oct 14, 2024
1 parent 581fab6 commit dac60e0
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 31 deletions.
4 changes: 4 additions & 0 deletions crates/uv-workspace/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub struct PyProjectToml {
pub project: Option<Project>,
/// Tool-specific metadata.
pub tool: Option<Tool>,
/// Non-project dependency groups, as defined in PEP 735.
pub dependency_groups: Option<BTreeMap<ExtraName, Vec<String>>>,
/// The raw unserialized document.
#[serde(skip)]
pub raw: String,
Expand Down Expand Up @@ -1053,6 +1055,8 @@ pub enum DependencyType {
Dev,
/// A dependency in `project.optional-dependencies.{0}`.
Optional(ExtraName),
/// A dependency in `dependency-groups.{0}`.
Group(ExtraName),
}

/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
Expand Down
18 changes: 17 additions & 1 deletion crates/uv-workspace/src/pyproject_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,14 +495,30 @@ impl PyProjectTomlMut {
}
}

// Check `dependency-groups`.
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
.get("tool")
.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() {
Expand Down
86 changes: 58 additions & 28 deletions crates/uv-workspace/src/workspace/tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::env;

use std::path::Path;
use std::str::FromStr;

use anyhow::Result;
use assert_fs::fixture::ChildPath;
use assert_fs::prelude::*;
use insta::assert_json_snapshot;

use uv_pep508::ExtraName;

use crate::pyproject::PyProjectToml;
use crate::workspace::{DiscoveryOptions, ProjectWorkspace};

async fn workspace_test(folder: &str) -> (ProjectWorkspace, String) {
Expand Down Expand Up @@ -43,7 +46,7 @@ async fn albatross_in_example() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-in-example/examples/bird-feeder",
"project_name": "bird-feeder",
Expand Down Expand Up @@ -75,11 +78,12 @@ async fn albatross_in_example() {
],
"optional-dependencies": null
},
"tool": null
"tool": null,
"dependency-groups": null
}
}
}
"#);
"###);
});
}

Expand All @@ -94,7 +98,7 @@ async fn albatross_project_in_excluded() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
"project_name": "bird-feeder",
Expand Down Expand Up @@ -126,11 +130,12 @@ async fn albatross_project_in_excluded() {
],
"optional-dependencies": null
},
"tool": null
"tool": null,
"dependency-groups": null
}
}
}
"#);
"###);
});
}

Expand All @@ -144,7 +149,7 @@ async fn albatross_root_workspace() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-root-workspace",
"project_name": "albatross",
Expand Down Expand Up @@ -233,11 +238,12 @@ async fn albatross_root_workspace() {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});
}

Expand All @@ -252,7 +258,7 @@ async fn albatross_virtual_workspace() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-virtual-workspace/packages/albatross",
"project_name": "albatross",
Expand Down Expand Up @@ -320,11 +326,12 @@ async fn albatross_virtual_workspace() {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});
}

Expand All @@ -338,7 +345,7 @@ async fn albatross_just_project() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-just-project",
"project_name": "albatross",
Expand Down Expand Up @@ -370,11 +377,12 @@ async fn albatross_just_project() {
],
"optional-dependencies": null
},
"tool": null
"tool": null,
"dependency-groups": null
}
}
}
"#);
"###);
});
}
#[tokio::test]
Expand Down Expand Up @@ -456,7 +464,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
Expand Down Expand Up @@ -519,11 +527,12 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});

// Rewrite the members to both include and exclude `bird-feeder` by name.
Expand Down Expand Up @@ -554,7 +563,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
Expand Down Expand Up @@ -618,11 +627,12 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});

// Rewrite the exclusion to use the top-level directory (`packages`).
Expand Down Expand Up @@ -653,7 +663,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
Expand Down Expand Up @@ -730,11 +740,12 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});

// Rewrite the exclusion to use the top-level directory with a glob (`packages/*`).
Expand Down Expand Up @@ -765,7 +776,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
Expand Down Expand Up @@ -816,12 +827,31 @@ async fn exclude_package() -> Result<()> {
"override-dependencies": null,
"constraint-dependencies": null
}
}
},
"dependency-groups": null
}
}
}
"#);
"###);
});

Ok(())
}

#[test]
fn 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()]);
}
6 changes: 6 additions & 0 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,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(_) => (),
}
}

Expand Down Expand Up @@ -451,6 +452,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.
Expand Down Expand Up @@ -710,6 +712,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;
Expand Down Expand Up @@ -779,6 +784,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(
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ pub(crate) async fn remove(
);
}
}
DependencyType::Group(_) => {
todo!("removing dependencies from groups is not yet supported")
}
}
}

Expand Down Expand Up @@ -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.
}
}
}
}
4 changes: 2 additions & 2 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit dac60e0

Please sign in to comment.