11#[ cfg( feature = "pep639-glob" ) ]
22mod pep639_glob;
3+ mod resolution;
34
45#[ cfg( feature = "pep639-glob" ) ]
56pub use pep639_glob:: { check_pep639_glob, parse_pep639_glob, Pep639GlobError } ;
6-
7- pub mod pep735_resolve;
7+ pub use resolution:: ResolveError ;
88
99use indexmap:: IndexMap ;
1010use pep440_rs:: { Version , VersionSpecifiers } ;
1111use pep508_rs:: Requirement ;
12+ use resolution:: resolve;
1213use serde:: { Deserialize , Serialize } ;
1314use std:: ops:: Deref ;
1415use std:: path:: PathBuf ;
@@ -42,6 +43,7 @@ pub struct PyProjectToml {
4243#[ serde( rename_all = "kebab-case" ) ]
4344pub 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.
203207pub struct DependencyGroups ( pub IndexMap < String , Vec < DependencyGroupSpecifier > > ) ;
204208
205209impl 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+
228242impl 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) ]
0 commit comments