Skip to content

Commit 261b5d2

Browse files
Support resolving optional dependencies and dependency groups (#30)
* Add a method to resolve recursion in optional dependencies * Make the trait internal and rely on providing project name explicitely in resolve * Unify recursion resolution, so that dependency groups can depend on optional dependency groups * Some fixes * Replicate proposed change, simplifying and inlining trait * Don't introduce OptionalDependencies * Simpmlify return type * Simplify resolve method by using split_off * reorder code for clarity * Handle the case where there is no parent, and make some items private * Rewrite --------- Co-authored-by: konstin <konstin@mailbox.org>
1 parent 6cf65d4 commit 261b5d2

File tree

4 files changed

+510
-151
lines changed

4 files changed

+510
-151
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "MIT"
77
keywords = ["pyproject", "pep517", "pep518", "pep621", "pep639"]
88
readme = "README.md"
99
repository = "https://github.com/PyO3/pyproject-toml-rs.git"
10-
rust-version = "1.64"
10+
rust-version = "1.74"
1111

1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313

src/lib.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
#[cfg(feature = "pep639-glob")]
22
mod pep639_glob;
3+
mod resolution;
34

45
#[cfg(feature = "pep639-glob")]
56
pub use pep639_glob::{check_pep639_glob, parse_pep639_glob, Pep639GlobError};
6-
7-
pub mod pep735_resolve;
7+
pub use resolution::ResolveError;
88

99
use indexmap::IndexMap;
1010
use pep440_rs::{Version, VersionSpecifiers};
1111
use pep508_rs::Requirement;
12+
use resolution::resolve;
1213
use serde::{Deserialize, Serialize};
1314
use std::ops::Deref;
1415
use std::path::PathBuf;
@@ -42,6 +43,7 @@ pub struct PyProjectToml {
4243
#[serde(rename_all = "kebab-case")]
4344
pub struct Project {
4445
/// The name of the project
46+
// TODO: This should become a `PackageName` in the next breaking release.
4547
pub name: String,
4648
/// The version of the project as supported by PEP 440
4749
pub version: Option<Version>,
@@ -83,6 +85,7 @@ pub struct Project {
8385
/// Project dependencies
8486
pub dependencies: Option<Vec<Requirement>>,
8587
/// Optional dependencies
88+
// TODO: The `String` should become a `ExtraName` in the next breaking release.
8689
pub optional_dependencies: Option<IndexMap<String, Vec<Requirement>>>,
8790
/// Specifies which fields listed by PEP 621 were intentionally unspecified
8891
/// so another tool can/will provide such metadata dynamically.
@@ -198,8 +201,9 @@ impl Contact {
198201
}
199202

200203
/// The `[dependency-groups]` section of pyproject.toml, as specified in PEP 735
201-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
204+
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
202205
#[serde(transparent)]
206+
// TODO: The `String` should become a `ExtraName` in the next breaking release.
203207
pub struct DependencyGroups(pub IndexMap<String, Vec<DependencyGroupSpecifier>>);
204208

205209
impl Deref for DependencyGroups {
@@ -225,11 +229,50 @@ pub enum DependencyGroupSpecifier {
225229
},
226230
}
227231

232+
/// Optional dependencies and dependency groups resolved into flat lists of requirements that are
233+
/// not self-referential
234+
///
235+
/// Note that `project.name` is required to resolve self-referential optional dependencies
236+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
237+
pub struct ResolvedDependencies {
238+
pub optional_dependencies: IndexMap<String, Vec<Requirement>>,
239+
pub dependency_groups: IndexMap<String, Vec<Requirement>>,
240+
}
241+
228242
impl PyProjectToml {
229243
/// Parse `pyproject.toml` content
230244
pub fn new(content: &str) -> Result<Self, toml::de::Error> {
231245
toml::de::from_str(content)
232246
}
247+
248+
/// Resolve the optional dependencies (extras) and dependency groups into flat lists of
249+
/// requirements.
250+
///
251+
/// This function will recursively resolve all optional dependency groups and dependency groups,
252+
/// including those that reference other groups. It will return an error if
253+
/// - there is a cycle in the groups, or
254+
/// - a group references another group that does not exist.
255+
///
256+
/// Resolving self-referential optional dependencies requires `project.name` to be set.
257+
///
258+
/// Note: This method makes no guarantee about the order of items and whether duplicates are
259+
/// removed or not.
260+
pub fn resolve(&self) -> Result<ResolvedDependencies, ResolveError> {
261+
let self_reference_name = self.project.as_ref().map(|p| p.name.as_str());
262+
let optional_dependencies = self
263+
.project
264+
.as_ref()
265+
.and_then(|p| p.optional_dependencies.as_ref());
266+
let dependency_groups = self.dependency_groups.as_ref();
267+
268+
let resolved_dependencies = resolve(
269+
self_reference_name,
270+
optional_dependencies,
271+
dependency_groups,
272+
)?;
273+
274+
Ok(resolved_dependencies)
275+
}
233276
}
234277

235278
#[cfg(test)]

src/pep735_resolve.rs

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)