mirror of
https://github.com/astral-sh/uv
synced 2026-06-21 13:47:25 +00:00
Add workspace-exclusive dependency groups to workspace metadata (#19862)
In a similar way that #19860 needs to add a top-level script node to represent dependencies of the script, it's been an outstanding issue that we lack a top-level workspace node to represent dependency-groups that are exclusive to the workspace.
This commit is contained in:
@@ -3,7 +3,7 @@ use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_distribution_types::{Name, RequiresPython, ResolvedDist, UrlString};
|
||||
use uv_distribution_types::{Name, Requirement, RequiresPython, ResolvedDist, UrlString};
|
||||
use uv_fs::PortablePathBuf;
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_pep440::Version;
|
||||
@@ -69,6 +69,9 @@ pub struct Metadata {
|
||||
/// Information about the script root, when metadata was requested for a script.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
script: Option<MetadataScript>,
|
||||
/// Information about the workspace root, when metadata was requested for a workspace.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
workspace: Option<MetadataWorkspace>,
|
||||
/// The version of python required by the workspace
|
||||
///
|
||||
/// Every `marker` we emit implicitly assumes this constraint to keep things clean
|
||||
@@ -120,6 +123,15 @@ struct MetadataScript {
|
||||
id: MetadataNodeIdFlat,
|
||||
}
|
||||
|
||||
/// The workspace entry-point.
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct MetadataWorkspace {
|
||||
/// Absolute path to the workspace root.
|
||||
path: PortablePathBuf,
|
||||
/// Key for the workspace's node in the `resolution` graph.
|
||||
id: MetadataNodeIdFlat,
|
||||
}
|
||||
|
||||
/// Info for looking up workspace members, most information is stored in the node behind `id`
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct MetadataWorkspaceMember {
|
||||
@@ -238,6 +250,30 @@ impl MetadataNode {
|
||||
node
|
||||
}
|
||||
|
||||
fn from_workspace(path: PortablePathBuf, dependency_groups: Vec<MetadataGroup>) -> Self {
|
||||
let mut node = Self::new(MetadataNodeId::Workspace(MetadataWorkspaceNodeId {
|
||||
kind: MetadataWorkspaceNodeKind::Workspace,
|
||||
path,
|
||||
}));
|
||||
node.dependency_groups = dependency_groups;
|
||||
node
|
||||
}
|
||||
|
||||
fn from_workspace_group(
|
||||
path: PortablePathBuf,
|
||||
group: GroupName,
|
||||
dependencies: Vec<MetadataDependency>,
|
||||
) -> Self {
|
||||
let mut node = Self::new(MetadataNodeId::WorkspaceGroup(
|
||||
MetadataWorkspaceGroupNodeId {
|
||||
kind: MetadataWorkspaceGroupNodeKind::Group(group),
|
||||
path,
|
||||
},
|
||||
));
|
||||
node.dependencies = dependencies;
|
||||
node
|
||||
}
|
||||
|
||||
fn add_dependency(&mut self, workspace_root: &PortablePathBuf, dependency: &Dependency) {
|
||||
let extras = dependency.extra();
|
||||
if extras.is_empty() {
|
||||
@@ -266,21 +302,33 @@ impl MetadataNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MetadataWorkspaceNodeKind {
|
||||
Workspace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MetadataWorkspaceGroupNodeKind {
|
||||
Group(GroupName),
|
||||
}
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MetadataScriptNodeKind {
|
||||
Script,
|
||||
}
|
||||
|
||||
fn script_root_dependencies(
|
||||
fn root_dependencies<'lock>(
|
||||
workspace_root: &PortablePathBuf,
|
||||
lock: &Lock,
|
||||
lock: &'lock Lock,
|
||||
requirements: impl IntoIterator<Item = &'lock Requirement>,
|
||||
) -> Vec<MetadataDependency> {
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// Root requirements retain names, extras, and markers rather than resolved package IDs. Match
|
||||
// them to the locked packages using the same name and fork-marker logic as lock export.
|
||||
for requirement in lock.requirements() {
|
||||
for requirement in requirements {
|
||||
for package in lock
|
||||
.packages()
|
||||
.iter()
|
||||
@@ -332,6 +380,8 @@ fn script_root_dependencies(
|
||||
enum MetadataNodeId {
|
||||
Package(MetadataPackageNodeId),
|
||||
Script(MetadataScriptNodeId),
|
||||
Workspace(MetadataWorkspaceNodeId),
|
||||
WorkspaceGroup(MetadataWorkspaceGroupNodeId),
|
||||
}
|
||||
|
||||
/// The unique key for a package-derived node.
|
||||
@@ -358,6 +408,22 @@ struct MetadataScriptNodeId {
|
||||
path: PortablePathBuf,
|
||||
}
|
||||
|
||||
/// The unique key for a workspace node.
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
struct MetadataWorkspaceNodeId {
|
||||
kind: MetadataWorkspaceNodeKind,
|
||||
/// Absolute path to the workspace root.
|
||||
path: PortablePathBuf,
|
||||
}
|
||||
|
||||
/// The unique key for a dependency group defined on the workspace root.
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
struct MetadataWorkspaceGroupNodeId {
|
||||
kind: MetadataWorkspaceGroupNodeKind,
|
||||
/// Absolute path to the workspace root.
|
||||
path: PortablePathBuf,
|
||||
}
|
||||
|
||||
/// This is intended to be an opaque unique id for referring to a node
|
||||
///
|
||||
/// It's human readable for convenience but parsing it or relying on it is inadvisable.
|
||||
@@ -384,7 +450,7 @@ impl MetadataNodeId {
|
||||
fn as_package(&self) -> Option<&MetadataPackageNodeId> {
|
||||
match self {
|
||||
Self::Package(package) => Some(package),
|
||||
Self::Script(_) => None,
|
||||
Self::Script(_) | Self::Workspace(_) | Self::WorkspaceGroup(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +471,11 @@ impl Display for MetadataNodeId {
|
||||
None => write!(f, "{}{}@{}", package.name, package.kind, package.source),
|
||||
},
|
||||
Self::Script(script) => write!(f, "script+{}", script.path),
|
||||
Self::Workspace(workspace) => write!(f, "workspace+{}", workspace.path),
|
||||
Self::WorkspaceGroup(group) => {
|
||||
let MetadataWorkspaceGroupNodeKind::Group(name) = &group.kind;
|
||||
write!(f, "workspace+{}:{name}", group.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -975,12 +1046,37 @@ impl Metadata {
|
||||
let path = PortablePathBuf::from(path);
|
||||
let node = MetadataNode::from_script(
|
||||
path.clone(),
|
||||
script_root_dependencies(&workspace_root, lock),
|
||||
root_dependencies(&workspace_root, lock, lock.requirements()),
|
||||
);
|
||||
let id = node.id.to_flat();
|
||||
resolve.insert(id.clone(), node);
|
||||
MetadataScript { path, id }
|
||||
});
|
||||
|
||||
let workspace_metadata = workspace.map(|_| {
|
||||
let mut dependency_groups = Vec::new();
|
||||
for (group, requirements) in lock.dependency_groups() {
|
||||
let node = MetadataNode::from_workspace_group(
|
||||
workspace_root.clone(),
|
||||
group.clone(),
|
||||
root_dependencies(&workspace_root, lock, requirements),
|
||||
);
|
||||
let id = node.id.to_flat();
|
||||
resolve.insert(id.clone(), node);
|
||||
dependency_groups.push(MetadataGroup {
|
||||
name: group.clone(),
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
let node = MetadataNode::from_workspace(workspace_root.clone(), dependency_groups);
|
||||
let id = node.id.to_flat();
|
||||
resolve.insert(id.clone(), node);
|
||||
MetadataWorkspace {
|
||||
path: workspace_root.clone(),
|
||||
id,
|
||||
}
|
||||
});
|
||||
let conflicts = MetadataConflicts::from_conflicts(&members, &resolve, &lock.conflicts);
|
||||
|
||||
Self {
|
||||
@@ -990,6 +1086,7 @@ impl Metadata {
|
||||
conflicts,
|
||||
environment: None,
|
||||
script,
|
||||
workspace: workspace_metadata,
|
||||
module_owners: BTreeMap::new(),
|
||||
workspace_root,
|
||||
requires_python: lock.requires_python.clone(),
|
||||
|
||||
@@ -86,6 +86,10 @@ fn workspace_metadata_simple() {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/foo",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/foo",
|
||||
"id": "workspace+[TEMP_DIR]/foo"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -106,6 +110,11 @@ fn workspace_metadata_simple() {
|
||||
},
|
||||
"kind": "package",
|
||||
"dependencies": []
|
||||
},
|
||||
"workspace+[TEMP_DIR]/foo": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/foo",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,6 +487,10 @@ dependencies = [
|
||||
"environment": {
|
||||
"root": "[VENV]/"
|
||||
},
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/",
|
||||
"id": "workspace+[TEMP_DIR]/"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -589,6 +602,11 @@ dependencies = [
|
||||
"filename": "typing_extensions-0.1.0-py3-none-any.whl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -781,6 +799,10 @@ fn workspace_metadata_root_workspace() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -907,6 +929,11 @@ fn workspace_metadata_root_workspace() -> Result<()> {
|
||||
"id": "idna==3.6@registry+https://pypi.org/simple"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -935,7 +962,13 @@ fn workspace_metadata_virtual_workspace() -> Result<()> {
|
||||
&workspace,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_metadata().current_dir(&workspace), @r#"
|
||||
let mut filters = context.filters();
|
||||
filters.push((
|
||||
r"(?m)^WARN Ignoring non-directory workspace member: `[^\n]+`\n",
|
||||
"",
|
||||
));
|
||||
|
||||
uv_snapshot!(filters, context.workspace_metadata().current_dir(&workspace), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
@@ -944,6 +977,10 @@ fn workspace_metadata_virtual_workspace() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1137,6 +1174,11 @@ fn workspace_metadata_virtual_workspace() -> Result<()> {
|
||||
"filename": "sniffio-1.3.1-py3-none-any.whl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1176,6 +1218,10 @@ fn workspace_metadata_from_member() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1302,6 +1348,11 @@ fn workspace_metadata_from_member() -> Result<()> {
|
||||
"id": "idna==3.6@registry+https://pypi.org/simple"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1350,6 +1401,10 @@ fn workspace_metadata_multiple_members() {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/pkg-a",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/pkg-a",
|
||||
"id": "workspace+[TEMP_DIR]/pkg-a"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1398,6 +1453,11 @@ fn workspace_metadata_multiple_members() {
|
||||
},
|
||||
"kind": "package",
|
||||
"dependencies": []
|
||||
},
|
||||
"workspace+[TEMP_DIR]/pkg-a": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/pkg-a",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1428,6 +1488,10 @@ fn workspace_metadata_single_project() {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/my-project",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/my-project",
|
||||
"id": "workspace+[TEMP_DIR]/my-project"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1448,6 +1512,11 @@ fn workspace_metadata_single_project() {
|
||||
},
|
||||
"kind": "package",
|
||||
"dependencies": []
|
||||
},
|
||||
"workspace+[TEMP_DIR]/my-project": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/my-project",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1483,6 +1552,10 @@ fn workspace_metadata_with_excluded() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1537,6 +1610,11 @@ fn workspace_metadata_with_excluded() -> Result<()> {
|
||||
"filename": "iniconfig-2.0.0-py3-none-any.whl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1551,7 +1629,7 @@ fn workspace_metadata_with_excluded() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test metadata with excluded packages.
|
||||
/// Test metadata for dependency groups defined on a non-package workspace root.
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn workspace_metadata_group_only() -> Result<()> {
|
||||
@@ -1574,6 +1652,10 @@ fn workspace_metadata_group_only() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1608,6 +1690,28 @@ fn workspace_metadata_group_only() -> Result<()> {
|
||||
"filename": "iniconfig-2.0.0-py3-none-any.whl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": [],
|
||||
"dependency_groups": [
|
||||
{
|
||||
"name": "dev",
|
||||
"id": "workspace+[TEMP_DIR]/workspace:dev"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace:dev": {
|
||||
"kind": {
|
||||
"group": "dev"
|
||||
},
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": [
|
||||
{
|
||||
"id": "iniconfig==2.0.0@registry+https://pypi.org/simple"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1663,6 +1767,10 @@ fn workspace_metadata_various_dependency_rainbow() -> Result<()> {
|
||||
"version": "preview"
|
||||
},
|
||||
"workspace_root": "[TEMP_DIR]/workspace",
|
||||
"workspace": {
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"id": "workspace+[TEMP_DIR]/workspace"
|
||||
},
|
||||
"requires_python": ">=3.12",
|
||||
"conflicts": {
|
||||
"sets": []
|
||||
@@ -1859,6 +1967,11 @@ fn workspace_metadata_various_dependency_rainbow() -> Result<()> {
|
||||
"filename": "sniffio-1.3.1-py3-none-any.whl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace+[TEMP_DIR]/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "[TEMP_DIR]/workspace",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,17 @@ for the node it refers to, and an optional `marker` that
|
||||
(if there is no marker the dependency is always required).
|
||||
|
||||
Package-derived nodes in the graph are uniquely identified by package `name`, `version`, `source`,
|
||||
and `kind`. Script nodes are identified by their path. All node ids should be treated as opaque.
|
||||
and `kind`. Script and workspace nodes are identified by their path. Workspace-root dependency group
|
||||
nodes are identified by their group name and the workspace path. All node ids should be treated as
|
||||
opaque.
|
||||
|
||||
There are 4 kinds of node in the graph:
|
||||
There are 5 kinds of node in the graph:
|
||||
|
||||
- `"script"` -- a PEP 723 script and its direct dependencies
|
||||
- `"workspace"` -- a workspace root and its workspace-exclusive dependency groups
|
||||
- `"package"` -- the package itself
|
||||
- `{ "extra": "extraname" }` -- an extra the package defines
|
||||
- `{ "group": "groupname" }` -- a dependency group the package defines
|
||||
- `{ "group": "groupname" }` -- a dependency group a package or workspace root defines
|
||||
|
||||
(In the future we will add "build" nodes for the dependencies of
|
||||
[build environments](https://docs.astral.sh/uv/concepts/projects/config/#build-isolation).)
|
||||
@@ -40,6 +43,9 @@ If you want to install the dependency group `mypackage:mygroup` then find the no
|
||||
`"kind": { "group": "mygroup" }` for `mypackage` (this node will _not_ depend on `mypackage`, as
|
||||
dependency groups are just lists of things you might want when working on the package itself).
|
||||
|
||||
If the workspace root defines dependency groups but is not itself a package, its `"workspace"` node
|
||||
provides the corresponding group node ids through `dependency_groups`.
|
||||
|
||||
## Handling multiple versions of a package
|
||||
|
||||
Two versions of a package cannot be installed into a python environment, but the dependency graph
|
||||
@@ -66,15 +72,16 @@ the graph and get actual resolutions you will likely need to consult `conflicts`
|
||||
understand how to resolve `markers` for a specific platform.
|
||||
|
||||
The best way to avoid mistakes when working with multiple versions of a package is to keep your
|
||||
queries into the dependency graph rooted in operations on workspace members or the requested script.
|
||||
These are the natural entry-points to the graph and can give coherent responses for operations such
|
||||
as "install `member1` and `member2[extra]`" or "install this script's declared dependencies".
|
||||
queries into the dependency graph rooted in operations on the workspace root, workspace members, or
|
||||
the requested script. These are the natural entry-points to the graph and can give coherent
|
||||
responses for operations such as "install the workspace `dev` group", "install `member1` and
|
||||
`member2[extra]`", or "install this script's declared dependencies".
|
||||
|
||||
Another way to put this is that when possible _you should avoid iterating over the `resolution`
|
||||
object to find a node_. Only access `resolution` like a map using ids that were provided by another
|
||||
part of the metadata. For a workspace, the initial ids are listed in the `members` array. For a
|
||||
script, the initial id is `script.id`. From there you may recursively discover other packages by
|
||||
following dependency edges.
|
||||
part of the metadata. For a workspace, the workspace root is `workspace.id` and package entry points
|
||||
are listed in the `members` array. For a script, the initial id is `script.id`. From there you may
|
||||
recursively discover other packages by following dependency edges.
|
||||
|
||||
So rather than trying to find a node for anyio in the dependency graph directly, you should decide
|
||||
what workspace member(s) you're interested in analyzing as if they were going to be installed. While
|
||||
@@ -93,6 +100,15 @@ group_node = metadata.resolution[group.id]
|
||||
visit(metadata, [group_node])
|
||||
```
|
||||
|
||||
For a dependency group defined on the workspace root, look it up through the workspace node:
|
||||
|
||||
```python
|
||||
workspace_node = metadata.resolution[metadata.workspace.id]
|
||||
group = find_by_name(workspace_node.dependency_groups, "dev")
|
||||
group_node = metadata.resolution[group.id]
|
||||
visit(metadata, [group_node])
|
||||
```
|
||||
|
||||
If you wanted to analyze two particular workspace members installed together, it would look
|
||||
something like:
|
||||
|
||||
@@ -158,13 +174,20 @@ Here is a human-readable annotated example:
|
||||
"root": "/workspace/.venv"
|
||||
},
|
||||
// Information about the script target, only present with `--script`.
|
||||
// Workspace metadata uses `members` below as its graph entry-points instead.
|
||||
// Workspace metadata uses `workspace` and `members` below as graph entry-points instead.
|
||||
"script": {
|
||||
// The absolute path to the script
|
||||
"path": "/workspace/script.py",
|
||||
// The id of the script's node in the `resolution` map below
|
||||
"id": "script+/workspace/script.py"
|
||||
},
|
||||
// Information about the workspace target, omitted when `--script` is used.
|
||||
"workspace": {
|
||||
// The absolute path to the workspace root
|
||||
"path": "/workspace",
|
||||
// The id of the workspace's node in the `resolution` map below
|
||||
"id": "workspace+/workspace"
|
||||
},
|
||||
// Any requirements on the python version this workspace has
|
||||
//
|
||||
// `marker` fields all have this as an implicit constraint that is omitted for cleanliness
|
||||
@@ -211,9 +234,10 @@ Here is a human-readable annotated example:
|
||||
// Resolved information about packages and dependencies.
|
||||
//
|
||||
// Each entry in this map is a node in the dependency graph. There are currently
|
||||
// 4 kinds of node in the dependency graph, although more are planned in the future.
|
||||
// 5 kinds of node in the dependency graph, although more are planned in the future.
|
||||
//
|
||||
// * Scripts -- "kind": "script"
|
||||
// * Workspaces -- "kind": "workspace"
|
||||
// * Packages -- "kind": "package"
|
||||
// * Extras -- "kind": { "extra": "extraname" }
|
||||
// * Groups -- "kind": { "group": "groupname" }
|
||||
@@ -242,6 +266,30 @@ Here is a human-readable annotated example:
|
||||
]
|
||||
},
|
||||
|
||||
// The workspace node owns metadata defined directly on the workspace root.
|
||||
"workspace+/workspace": {
|
||||
"kind": "workspace",
|
||||
"path": "/workspace",
|
||||
"dependencies": [],
|
||||
"dependency_groups": [
|
||||
{
|
||||
"name": "dev",
|
||||
"id": "workspace+/workspace:dev"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// This node is a dependency group defined on the non-package workspace root.
|
||||
"workspace+/workspace:dev": {
|
||||
"kind": { "group": "dev" },
|
||||
"path": "/workspace",
|
||||
"dependencies": [
|
||||
{
|
||||
"id": "iniconfig==2.0.0@registry+https://pypi.org/simple"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// This node is a workspace member
|
||||
"mypackage==0.1.0@editable+/workspace/packages/mypackage": {
|
||||
// The name of the package
|
||||
|
||||
Reference in New Issue
Block a user