@@ -13,7 +13,7 @@ use uv_fs::{absolutize_path, Simplified};
1313use uv_normalize:: PackageName ;
1414use uv_warnings:: warn_user;
1515
16- use crate :: pyproject:: { PyProjectToml , Source , ToolUvWorkspace } ;
16+ use crate :: pyproject:: { Project , PyProjectToml , Source , ToolUvWorkspace } ;
1717
1818#[ derive( thiserror:: Error , Debug ) ]
1919pub enum WorkspaceError {
@@ -118,7 +118,11 @@ impl Workspace {
118118 let current_project = pyproject_toml
119119 . project
120120 . clone ( )
121- . map ( |project| ( project. name . clone ( ) , project_path, pyproject_toml) ) ;
121+ . map ( |project| WorkspaceMember {
122+ root : project_path,
123+ project,
124+ pyproject_toml,
125+ } ) ;
122126 Self :: collect_members (
123127 workspace_root,
124128 workspace_definition,
@@ -162,7 +166,7 @@ impl Workspace {
162166 workspace_root : PathBuf ,
163167 workspace_definition : ToolUvWorkspace ,
164168 workspace_pyproject_toml : PyProjectToml ,
165- current_project : Option < ( PackageName , PathBuf , PyProjectToml ) > ,
169+ current_project : Option < WorkspaceMember > ,
166170 stop_discovery_at : Option < & Path > ,
167171 ) -> Result < Workspace , WorkspaceError > {
168172 let mut workspace_members = BTreeMap :: new ( ) ;
@@ -173,7 +177,7 @@ impl Workspace {
173177 // project.
174178 if current_project
175179 . as_ref ( )
176- . map ( |( _ , path , _ ) | path != & workspace_root)
180+ . map ( |root_member| root_member . root != workspace_root)
177181 . unwrap_or ( true )
178182 {
179183 if let Some ( project) = & workspace_pyproject_toml. project {
@@ -192,27 +196,22 @@ impl Workspace {
192196 project. name . clone ( ) ,
193197 WorkspaceMember {
194198 root : workspace_root. clone ( ) ,
199+ project : project. clone ( ) ,
195200 pyproject_toml,
196201 } ,
197202 ) ;
198203 } ;
199204 }
200205
201206 // The current project is a workspace member, especially in a single project workspace.
202- if let Some ( ( project_name , project_path , project ) ) = current_project {
207+ if let Some ( root_member ) = current_project {
203208 debug ! (
204209 "Adding current workspace member: {}" ,
205- project_path . simplified_display( )
210+ root_member . root . simplified_display( )
206211 ) ;
207212
208- seen. insert ( project_path. clone ( ) ) ;
209- workspace_members. insert (
210- project_name,
211- WorkspaceMember {
212- root : project_path. clone ( ) ,
213- pyproject_toml : project. clone ( ) ,
214- } ,
215- ) ;
213+ seen. insert ( root_member. root . clone ( ) ) ;
214+ workspace_members. insert ( root_member. project . name . clone ( ) , root_member) ;
216215 }
217216
218217 // Add all other workspace members.
@@ -247,16 +246,18 @@ impl Workspace {
247246 return Err ( WorkspaceError :: MissingProject ( member_root) ) ;
248247 } ;
249248
250- let member = WorkspaceMember {
251- root : member_root. clone ( ) ,
252- pyproject_toml,
253- } ;
254-
255249 debug ! (
256250 "Adding discovered workspace member: {}" ,
257251 member_root. simplified_display( )
258252 ) ;
259- workspace_members. insert ( project. name , member) ;
253+ workspace_members. insert (
254+ project. name . clone ( ) ,
255+ WorkspaceMember {
256+ root : member_root. clone ( ) ,
257+ project,
258+ pyproject_toml,
259+ } ,
260+ ) ;
260261 }
261262 }
262263 let workspace_sources = workspace_pyproject_toml
@@ -281,6 +282,9 @@ impl Workspace {
281282pub struct WorkspaceMember {
282283 /// The path to the project root.
283284 root : PathBuf ,
285+ /// The `[project]` table, from the `pyproject.toml` of the project found at
286+ /// `<root>/pyproject.toml`.
287+ project : Project ,
284288 /// The `pyproject.toml` of the project, found at `<root>/pyproject.toml`.
285289 pyproject_toml : PyProjectToml ,
286290}
@@ -291,6 +295,12 @@ impl WorkspaceMember {
291295 & self . root
292296 }
293297
298+ /// The `[project]` table, from the `pyproject.toml` of the project found at
299+ /// `<root>/pyproject.toml`.
300+ pub fn project ( & self ) -> & Project {
301+ & self . project
302+ }
303+
294304 /// The `pyproject.toml` of the project, found at `<root>/pyproject.toml`.
295305 pub fn pyproject_toml ( & self ) -> & PyProjectToml {
296306 & self . pyproject_toml
@@ -430,13 +440,7 @@ impl ProjectWorkspace {
430440 . clone ( )
431441 . ok_or_else ( || WorkspaceError :: MissingProject ( pyproject_path. clone ( ) ) ) ?;
432442
433- Self :: from_project (
434- project_root,
435- & pyproject_toml,
436- project. name ,
437- stop_discovery_at,
438- )
439- . await
443+ Self :: from_project ( project_root, & project, & pyproject_toml, stop_discovery_at) . await
440444 }
441445
442446 /// If the current directory contains a `pyproject.toml` with a `project` table, discover the
@@ -461,13 +465,7 @@ impl ProjectWorkspace {
461465 } ;
462466
463467 Ok ( Some (
464- Self :: from_project (
465- project_root,
466- & pyproject_toml,
467- project. name ,
468- stop_discovery_at,
469- )
470- . await ?,
468+ Self :: from_project ( project_root, & project, & pyproject_toml, stop_discovery_at) . await ?,
471469 ) )
472470 }
473471
@@ -519,43 +517,51 @@ impl ProjectWorkspace {
519517 /// Find the workspace for a project.
520518 pub async fn from_project (
521519 project_path : & Path ,
522- project : & PyProjectToml ,
523- project_name : PackageName ,
520+ project : & Project ,
521+ project_pyproject_toml : & PyProjectToml ,
524522 stop_discovery_at : Option < & Path > ,
525523 ) -> Result < Self , WorkspaceError > {
526524 let project_path = absolutize_path ( project_path)
527525 . map_err ( WorkspaceError :: Normalize ) ?
528526 . to_path_buf ( ) ;
529527
530528 // Check if the current project is also an explicit workspace root.
531- let mut workspace = project
529+ let mut workspace = project_pyproject_toml
532530 . tool
533531 . as_ref ( )
534532 . and_then ( |tool| tool. uv . as_ref ( ) )
535533 . and_then ( |uv| uv. workspace . as_ref ( ) )
536- . map ( |workspace| ( project_path. clone ( ) , workspace. clone ( ) , project. clone ( ) ) ) ;
534+ . map ( |workspace| {
535+ (
536+ project_path. clone ( ) ,
537+ workspace. clone ( ) ,
538+ project_pyproject_toml. clone ( ) ,
539+ )
540+ } ) ;
537541
538542 if workspace. is_none ( ) {
539543 // The project isn't an explicit workspace root, check if we're a regular workspace
540544 // member by looking for an explicit workspace root above.
541545 workspace = find_workspace ( & project_path, stop_discovery_at) . await ?;
542546 }
543547
548+ let current_project = WorkspaceMember {
549+ root : project_path. clone ( ) ,
550+ project : project. clone ( ) ,
551+ pyproject_toml : project_pyproject_toml. clone ( ) ,
552+ } ;
553+
544554 let Some ( ( workspace_root, workspace_definition, workspace_pyproject_toml) ) = workspace
545555 else {
546556 // The project isn't an explicit workspace root, but there's also no workspace root
547557 // above it, so the project is an implicit workspace root identical to the project root.
548558 debug ! ( "No workspace root found, using project root" ) ;
549- let current_project_as_members = BTreeMap :: from_iter ( [ (
550- project_name. clone ( ) ,
551- WorkspaceMember {
552- root : project_path. clone ( ) ,
553- pyproject_toml : project. clone ( ) ,
554- } ,
555- ) ] ) ;
559+
560+ let current_project_as_members =
561+ BTreeMap :: from_iter ( [ ( project. name . clone ( ) , current_project) ] ) ;
556562 return Ok ( Self {
557563 project_root : project_path. clone ( ) ,
558- project_name : project_name . clone ( ) ,
564+ project_name : project . name . clone ( ) ,
559565 workspace : Workspace {
560566 root : project_path. clone ( ) ,
561567 packages : current_project_as_members,
@@ -575,14 +581,14 @@ impl ProjectWorkspace {
575581 workspace_root,
576582 workspace_definition,
577583 workspace_pyproject_toml,
578- Some ( ( project_name . clone ( ) , project_path . clone ( ) , project . clone ( ) ) ) ,
584+ Some ( current_project ) ,
579585 stop_discovery_at,
580586 )
581587 . await ?;
582588
583589 Ok ( Self {
584590 project_root : project_path,
585- project_name,
591+ project_name : project . name . clone ( ) ,
586592 workspace,
587593 } )
588594 }
@@ -818,6 +824,11 @@ mod tests {
818824 "packages": {
819825 "bird-feeder": {
820826 "root": "[ROOT]/albatross-in-example/examples/bird-feeder",
827+ "project": {
828+ "name": "bird-feeder",
829+ "requires-python": null,
830+ "optional-dependencies": null
831+ },
821832 "pyproject_toml": "[PYPROJECT_TOML]"
822833 }
823834 },
@@ -848,6 +859,11 @@ mod tests {
848859 "packages": {
849860 "bird-feeder": {
850861 "root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
862+ "project": {
863+ "name": "bird-feeder",
864+ "requires-python": null,
865+ "optional-dependencies": null
866+ },
851867 "pyproject_toml": "[PYPROJECT_TOML]"
852868 }
853869 },
@@ -877,14 +893,29 @@ mod tests {
877893 "packages": {
878894 "albatross": {
879895 "root": "[ROOT]/albatross-root-workspace",
896+ "project": {
897+ "name": "albatross",
898+ "requires-python": null,
899+ "optional-dependencies": null
900+ },
880901 "pyproject_toml": "[PYPROJECT_TOML]"
881902 },
882903 "bird-feeder": {
883904 "root": "[ROOT]/albatross-root-workspace/packages/bird-feeder",
905+ "project": {
906+ "name": "bird-feeder",
907+ "requires-python": null,
908+ "optional-dependencies": null
909+ },
884910 "pyproject_toml": "[PYPROJECT_TOML]"
885911 },
886912 "seeds": {
887913 "root": "[ROOT]/albatross-root-workspace/packages/seeds",
914+ "project": {
915+ "name": "seeds",
916+ "requires-python": null,
917+ "optional-dependencies": null
918+ },
888919 "pyproject_toml": "[PYPROJECT_TOML]"
889920 }
890921 },
@@ -920,14 +951,29 @@ mod tests {
920951 "packages": {
921952 "albatross": {
922953 "root": "[ROOT]/albatross-virtual-workspace/packages/albatross",
954+ "project": {
955+ "name": "albatross",
956+ "requires-python": null,
957+ "optional-dependencies": null
958+ },
923959 "pyproject_toml": "[PYPROJECT_TOML]"
924960 },
925961 "bird-feeder": {
926962 "root": "[ROOT]/albatross-virtual-workspace/packages/bird-feeder",
963+ "project": {
964+ "name": "bird-feeder",
965+ "requires-python": null,
966+ "optional-dependencies": null
967+ },
927968 "pyproject_toml": "[PYPROJECT_TOML]"
928969 },
929970 "seeds": {
930971 "root": "[ROOT]/albatross-virtual-workspace/packages/seeds",
972+ "project": {
973+ "name": "seeds",
974+ "requires-python": null,
975+ "optional-dependencies": null
976+ },
931977 "pyproject_toml": "[PYPROJECT_TOML]"
932978 }
933979 },
@@ -957,6 +1003,11 @@ mod tests {
9571003 "packages": {
9581004 "albatross": {
9591005 "root": "[ROOT]/albatross-just-project",
1006+ "project": {
1007+ "name": "albatross",
1008+ "requires-python": null,
1009+ "optional-dependencies": null
1010+ },
9601011 "pyproject_toml": "[PYPROJECT_TOML]"
9611012 }
9621013 },
0 commit comments