Skip to content

Expand uv workspace metadata with dependency information from the lock#18356

Merged
Gankra merged 16 commits intomainfrom
project-json
Mar 27, 2026
Merged

Expand uv workspace metadata with dependency information from the lock#18356
Gankra merged 16 commits intomainfrom
project-json

Conversation

@Gankra
Copy link
Copy Markdown
Contributor

@Gankra Gankra commented Mar 6, 2026

Summary

This expands uv workspace metadata with many of the fields that are found in uv.lock so that we have a format with information about the dependency graph/resolution that we're willing to call stable and have people rely upon (rather than uv.lock which we'd rather you don't try to interpret).

To a first approximation you can think of this as "uv.lock but serialized to json" but with the fields a bit more limited for now (easy to add later).

The biggest intentional divergence with uv.lock is that we favour encoding the dependency graph in a form that looks more like our internal "resolve" graph, in that hopes that it will simplify the work of anyone doing analysis on the graph (we structure our internal graph like this for a reason).

Specifically, the resolve field contains the entire dependency graph, with packages desugarred into several different nodes. There are 4 kinds of nodes (really 3, the build nodes will only be introduced when we establish build-dependency locking):

  • packages: mypackage==1.0.0 @ registry+https://pypi.org/simple
  • extras: mypackage[myextra]==1.0.0 @ registry+https://pypi.org/simple
  • groups: mypackage:mygroup==1.0.0 @ registry+https://pypi.org/simple
  • build: mypackage(build)==1.0.0 @ registry+https://pypi.org/simple

package nodes hold additional metadata about the package itself, and ids of the associated extra/group/build nodes.


A package like this:

[project]
name = "mypackage"
version = "1.0.0"

dependencies = ["httpx"]

[project.optional-dependencies]
cli = ["rich"]

[dependency-groups]
dev = ["typing-extensions"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

will get 4 nodes with the following edges (Version and Source omitted here for brevity):

  • mypackage
    • httpx
  • mypackage(build)
    • hatchling
  • mypackage[cli]
    • mypackage
    • rich
  • mypackage:dev
    • typing-extensions

Note that mypackage[cli] has a dependency edge on mypackage while mypackage:dev does not. This is because
mypackage[cli] is fundamentally an augmentation of mypackage while mypackage:dev is just a list of packages that happens to be defined by mypackage's pyproject.toml.

The resulting nodes for mypackage will look something like:

json blob
{
  "resolve": {
    "mypackage==1.0.0 @ editable+.": {
      "name": "mypackage",
      "version": "1.0.0",
      "source": {
        "editable": "."
      },
      "kind": "package",
      "dependencies": [
        {
          "id": "httpx==3.6 @ registry+https://pypi.org/simple"
          "marker": "sys_platform == 'linux'"
        },
      ],
      "optional_dependencies": [
        {
          "name": "cli",
          "id": "mypackage[cli]==1.0.0 @ editable+."
        },
      ],
      "dependency_groups": [
        {
          "name": "dev",
          "id": "mypackage:dev==1.0.0 @ editable+."
        }
      ]
      "build_system": {
        "build_backend": "hatchling.build",
        "id": "mypackage(build)==1.0.0 @ editable+."
      }
      "sdist": { ... },
      "wheels": [ ... ]
    },
    "mypackage:dev==1.0.0 @ editable+.": {
        "name": "mypackage",
        "version": "1.0.0",
        "source": {
          "editable": "."
        },
        "kind": {
          "group": "dev"
        },
        "dependencies": [
          {
            "id": "typing-extensions==1.2.3 @ registry+https://pypi.org/simple"
          },
        ]
      },
   }
   "mypackage[cli]==1.0.0 @ editable+.": {
      "name": "mypackage",
      "version": "1.0.0",
      "source": {
        "editable": "."
      },
      "kind": {
        "extra": "cli"
      },
      "dependencies": [
        {
          "id": "rich==2.2.3 @ registry+https://pypi.org/simple"
        },
        {
          "id": "mypackage==1.0.0 @ editable+."
        },
      ]
    },
    "mypackage(build)==1.0.0 @ editable+.": {
      "name": "mypackage",
      "version": "1.0.0",
      "source": {
        "editable": "."
      },
      "kind": "build",
      "dependencies": [
        {
          "id": "hatchling==3.2.3 @ registry+https://pypi.org/simple"
        },
      ]
    }
  }
}

Test Plan

Snapshots

@Gankra Gankra requested a review from konstin March 6, 2026 18:50
@Gankra Gankra added the enhancement New feature or improvement to existing functionality label Mar 6, 2026
@Gankra
Copy link
Copy Markdown
Contributor Author

Gankra commented Mar 6, 2026

Note that if the desugared "resolve" thing is too weird it's really easy for me to inline the virtual nodes back into the package nodes -- they are after all basically just an array of dependencies.

My motivation for desugarring is because similar sugaring of nodes in cargo metadata leads to confusion, as people don't understand that mypackage and mypackage(test) or mypackage(build) should be analyzed as distinct nodes. Hence this long screed I had to write in guppy.

@konstin
Copy link
Copy Markdown
Member

konstin commented Mar 10, 2026

Can you add what kinds of queries we users to be able to run against this graph? Such as "Give me all the wheels that would be installed on linux (to audit them)", or "What packages are outdated", etc.

@@ -319,3 +839,2610 @@ fn workspace_metadata_no_project() {
"
);
}

/// Test packse (has optional-dependencies, dev-dependencies, and build-system)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That snapshot is over 2000 lines long, can we use something shorter that's easier to check?

registry: MetadataRegistrySource::Path(PortablePathBuf::from(path)),
},
},
Source::Git(url, _) => Self::Git { git: url },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we record the resolved git info too?

Copy link
Copy Markdown
Contributor Author

@Gankra Gankra Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source::to_toml (which is the format we use to write the lock to disk?) throws out this info so I took it to be some extra-internal metadata that isn't worth exposing -- no strong opinion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems nice to unpack some of this here. It's bloat in the lockfile but probably saves someone some trouble in downstream integrations?

@elephantum
Copy link
Copy Markdown

I'm trying to grok if this solves my need, help me understand, please:

I need to inspect uv workspace, find all the ws members and their relations with each other, and their locations relative to ws root
I'm building CI/CD pipelines codegen, so that each workspace member has it's own CI pipeline according to a specific template

What concerns me here is that I do not see a signal that this specific package is a workspace member, but maybe I'm missing something

@Gankra
Copy link
Copy Markdown
Contributor Author

Gankra commented Mar 27, 2026

Ok I've addressed all concerns, the one thing I've been poking at is that our behaviour for virtual workspaces (dependency-groups only pyproject.tomls) is wonky, but this is reflected in the fact that we simply don't properly track/record those nodes in the Lock at all. All the top-level dependency-groups are filed off and the constraints are seemingly just added loose in the graph.

The ultimate result is that we get back a resolve graph that contains the packages-the-dependency-groups-would-install without any context on the existence of the dependency-groups or how those packages relate to them.

While it's straightforward for me to detect this and synthesize a node for the virtual pyproject.toml, the actual lock doesn't remember the existence of these groups, so I cannot properly define the edges from the groups to the actual packages they install (without just like, Guessing). This requires some actual non-trivial changes to how we feed these things into the resolver.

I think we should punt on that for now and ship this PR as-is.

@Gankra Gankra merged commit 202e0f0 into main Mar 27, 2026
111 checks passed
@Gankra Gankra deleted the project-json branch March 27, 2026 13:22
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Apr 2, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [uv](https://github.com/astral-sh/uv) | minor | `0.10.9` → `0.11.3` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (uv)</summary>

### [`v0.11.3`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0113)

[Compare Source](astral-sh/uv@0.11.2...0.11.3)

Released on 2026-04-01.

##### Enhancements

- Add progress bar for hashing phase in uv publish ([#&#8203;18752](astral-sh/uv#18752))
- Add support for ROCm 7.2 ([#&#8203;18730](astral-sh/uv#18730))
- Emit abi3t tags for every abi3 version ([#&#8203;18777](astral-sh/uv#18777))
- Expand `uv workspace metadata` with dependency information from the lock ([#&#8203;18356](astral-sh/uv#18356))
- Implement support for PEP 803 ([#&#8203;18767](astral-sh/uv#18767))
- Pretty-print platform in built wheel errors ([#&#8203;18738](astral-sh/uv#18738))
- Publish installers to `/installers/uv/latest` on the mirror ([#&#8203;18725](astral-sh/uv#18725))
- Show free-threaded Python in built-wheel errors ([#&#8203;18740](astral-sh/uv#18740))

##### Preview features

- Add `--ignore` and `--ignore-until-fixed` to `uv audit` ([#&#8203;18737](astral-sh/uv#18737))

##### Bug fixes

- Bump simple API cache ([#&#8203;18797](astral-sh/uv#18797))
- Don't drop `blake2b` hashes ([#&#8203;18794](astral-sh/uv#18794))
- Handle broken range request implementations ([#&#8203;18780](astral-sh/uv#18780))
- Remove `powerpc64-unknown-linux-gnu` from release build targets ([#&#8203;18800](astral-sh/uv#18800))
- Respect dependency metadata overrides in `uv pip check` ([#&#8203;18742](astral-sh/uv#18742))
- Support debug CPython ABI tags in environment compatibility ([#&#8203;18739](astral-sh/uv#18739))

##### Documentation

- Document `false` opt-out for `exclude-newer-package` ([#&#8203;18768](astral-sh/uv#18768), [#&#8203;18803](astral-sh/uv#18803))

### [`v0.11.2`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0112)

[Compare Source](astral-sh/uv@0.11.1...0.11.2)

Released on 2026-03-26.

##### Enhancements

- Add a dedicated Windows PE editing error ([#&#8203;18710](astral-sh/uv#18710))
- Make `uv self update` fetch the manifest from the mirror first ([#&#8203;18679](astral-sh/uv#18679))
- Use uv reqwest client for self update ([#&#8203;17982](astral-sh/uv#17982))
- Show `uv self update` success and failure messages with `--quiet` ([#&#8203;18645](astral-sh/uv#18645))

##### Preview features

- Evaluate extras and groups when determining auditable packages ([#&#8203;18511](astral-sh/uv#18511))

##### Bug fixes

- Skip redundant project configuration parsing for `uv run` ([#&#8203;17890](astral-sh/uv#17890))

### [`v0.11.1`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0111)

[Compare Source](astral-sh/uv@0.11.0...0.11.1)

Released on 2026-03-24.

##### Bug fixes

- Add missing hash verification for `riscv64gc-unknown-linux-musl` ([#&#8203;18686](astral-sh/uv#18686))
- Fallback to direct download when direct URL streaming is unsupported ([#&#8203;18688](astral-sh/uv#18688))
- Revert treating 'Dynamic' values as case-insensitive ([#&#8203;18692](astral-sh/uv#18692))
- Remove torchdata from list of packages to source from the PyTorch index ([#&#8203;18703](astral-sh/uv#18703))
- Special-case `==` Python version request ranges ([#&#8203;9697](astral-sh/uv#9697))

##### Documentation

- Cover `--python <dir>` in "Using arbitrary Python environments" ([#&#8203;6457](astral-sh/uv#6457))
- Fix version annotations for `PS_MODULE_PATH` and `UV_WORKING_DIR` ([#&#8203;18691](astral-sh/uv#18691))

### [`v0.11.0`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0110)

[Compare Source](astral-sh/uv@0.10.12...0.11.0)

Released on 2026-03-23.

##### Breaking changes

This release includes changes to the networking stack used by uv. While we think that breakage will be rare, it is possible that these changes will result in the rejection of certificates previously trusted by uv so we have marked the change as breaking out of an abundance of caution.

The changes are largely driven by the upgrade of reqwest, which powers uv's HTTP clients, to [v0.13](https://seanmonstar.com/blog/reqwest-v013-rustls-default/) which included some breaking changes to TLS certificate verification.

The following changes are included:

- [`rustls-platform-verifier`](https://github.com/rustls/rustls-platform-verifier) is used instead of [`rustls-native-certs`](https://github.com/rustls/rustls-native-certs) and [`webpki`](https://github.com/rustls/webpki) for certificate verification

  **This change should have no effect unless you are using the `native-tls` option to enable reading system certificates.**

  `rustls-platform-verifier` delegates to the system for certificate validation (e.g., `Security.framework` on macOS) instead of eagerly loading certificates from the system and verifying them via `webpki`. The effects of this change will vary based on the operating system. In general, uv's certificate validation should now be more consistent with browsers and other native applications. However, this is the most likely cause of breaking changes in this release. Some previously failing certificate chains may succeed, and some previously accepted certificate chains may fail. In either case, we expect the validation to be more correct and welcome reports of regressions.

  In particular, because more responsibility for validating the certificate is transferred to your system's security library, some features like [CA constraints](https://support.apple.com/en-us/103255) or [revocation of certificates](https://en.wikipedia.org/wiki/Certificate_revocation) via OCSP and CRLs may now be used.

  This change should improve performance when using system certificate on macOS, as uv no longer needs to load all certificates from the keychain at startup.
- [`aws-lc`](https://github.com/aws/aws-lc) is used instead of `ring` for a cryptography backend

  There should not be breaking changes from this change. We expect this to expand support for certificate signature algorithms.
- `--native-tls` is deprecated in favor of a new `--system-certs` flag

  The `--native-tls` flag is still usable and has identical behavior to `--system-certs.`

  This change was made to reduce confusion about the TLS implementation uv uses. uv always uses `rustls` not `native-tls`.
- Building uv on x86-64 and i686 Windows requires NASM

  NASM is required by `aws-lc`. If not found on the system, a prebuilt blob provided by `aws-lc-sys` will be used.

  If you are not building uv from source, this change has no effect.

  See the [CONTRIBUTING](https://github.com/astral-sh/uv/blob/b6854d77bfd0cb78157fecaf8b30126c6f16bc11/CONTRIBUTING.md#setup) guide for details.
- Empty `SSL_CERT_FILE` values are ignored (for consistency with `SSL_CERT_DIR`)

See [#&#8203;18550](astral-sh/uv#18550) for details.

##### Python

- Enable frame pointers for improved profiling on Linux x86-64 and aarch64

See the [python-build-standalone release notes](https://github.com/astral-sh/python-build-standalone/releases/20260320) for details.

##### Enhancements

- Treat 'Dynamic' values as case-insensitive ([#&#8203;18669](astral-sh/uv#18669))
- Use a dedicated error for invalid cache control headers ([#&#8203;18657](astral-sh/uv#18657))
- Enable checksum verification in the generated installer script ([#&#8203;18625](astral-sh/uv#18625))

##### Preview features

- Add `--service-format` and `--service-url` to `uv audit` ([#&#8203;18571](astral-sh/uv#18571))

##### Performance

- Avoid holding flat index lock across indexes ([#&#8203;18659](astral-sh/uv#18659))

##### Bug fixes

- Find the dynamic linker on the file system when sniffing binaries fails ([#&#8203;18457](astral-sh/uv#18457))
- Fix export of conflicting workspace members with dependencies ([#&#8203;18666](astral-sh/uv#18666))
- Respect installed settings in `uv tool list --outdated` ([#&#8203;18586](astral-sh/uv#18586))
- Treat paths originating as PEP 508 URLs which contain expanded variables as relative ([#&#8203;18680](astral-sh/uv#18680))
- Fix `uv export` for workspace member packages with conflicts ([#&#8203;18635](astral-sh/uv#18635))
- Continue to alternative authentication providers when the pyx store has no token ([#&#8203;18425](astral-sh/uv#18425))
- Use redacted URLs for log messages in cached client ([#&#8203;18599](astral-sh/uv#18599))

##### Documentation

- Add details on Linux versions to the platform policy ([#&#8203;18574](astral-sh/uv#18574))
- Clarify `FLASH_ATTENTION_SKIP_CUDA_BUILD` guidance for `flash-attn` installs ([#&#8203;18473](astral-sh/uv#18473))
- Split the dependency bots page into two separate pages ([#&#8203;18597](astral-sh/uv#18597))
- Split the alternative indexes page into separate pages ([#&#8203;18607](astral-sh/uv#18607))

### [`v0.10.12`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#01012)

[Compare Source](astral-sh/uv@0.10.11...0.10.12)

Released on 2026-03-19.

##### Python

- Add pypy 3.11.15 ([#&#8203;18468](astral-sh/uv#18468))
- Add support for using Python 3.6 interpreters ([#&#8203;18454](astral-sh/uv#18454))

##### Enhancements

- Include uv's target triple in version report ([#&#8203;18520](astral-sh/uv#18520))
- Allow comma separated values in `--no-emit-package` ([#&#8203;18565](astral-sh/uv#18565))

##### Preview features

- Show `uv audit` in the CLI help ([#&#8203;18540](astral-sh/uv#18540))

##### Bug fixes

- Improve reporting of managed interpreter symlinks in `uv python list` ([#&#8203;18459](astral-sh/uv#18459))
- Preserve end-of-line comments on previous entries when removing dependencies ([#&#8203;18557](astral-sh/uv#18557))
- Treat abi3 wheel Python version as a lower bound ([#&#8203;18536](astral-sh/uv#18536))
- Detect hard-float support on aarch64 kernels running armv7 userspace ([#&#8203;18530](astral-sh/uv#18530))

##### Documentation

- Add Python 3.15 to supported versions ([#&#8203;18552](astral-sh/uv#18552))
- Adjust the PyPy note ([#&#8203;18548](astral-sh/uv#18548))
- Move Pyodide to Tier 2 in the Python support policy ([#&#8203;18561](astral-sh/uv#18561))
- Move Rust and Python version support out of the Platform support policy ([#&#8203;18535](astral-sh/uv#18535))
- Update Docker guide with changes from `uv-docker-example` ([#&#8203;18558](astral-sh/uv#18558))
- Update the Python version policy ([#&#8203;18559](astral-sh/uv#18559))

### [`v0.10.11`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#01011)

[Compare Source](astral-sh/uv@0.10.10...0.10.11)

Released on 2026-03-16.

##### Enhancements

- Fetch Ruff release metadata from an Astral mirror ([#&#8203;18358](astral-sh/uv#18358))
- Use PEP 639 license metadata for uv itself ([#&#8203;16477](astral-sh/uv#16477))

##### Performance

- Improve distribution id performance ([#&#8203;18486](astral-sh/uv#18486))

##### Bug fixes

- Allow `--project` to refer to a `pyproject.toml` directly and reduce to a warning on other files ([#&#8203;18513](astral-sh/uv#18513))
- Disable `SYSTEM_VERSION_COMPAT` when querying interpreters on macOS ([#&#8203;18452](astral-sh/uv#18452))
- Enforce available distributions for supported environments ([#&#8203;18451](astral-sh/uv#18451))
- Fix `uv sync --active` recreating active environments when `UV_PYTHON_INSTALL_DIR` is relative ([#&#8203;18398](astral-sh/uv#18398))

##### Documentation

- Add missing `-o requirements.txt` in `uv pip compile` example ([#&#8203;12308](astral-sh/uv#12308))
- Link to organization security policy ([#&#8203;18449](astral-sh/uv#18449))
- Link to the AI policy in the contributing guide ([#&#8203;18448](astral-sh/uv#18448))

### [`v0.10.10`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#01010)

[Compare Source](astral-sh/uv@0.10.9...0.10.10)

Released on 2026-03-13.

##### Python

- Add CPython 3.15.0a7 ([#&#8203;18403](astral-sh/uv#18403))

##### Enhancements

- Add `--outdated` flag to `uv tool list` ([#&#8203;18318](astral-sh/uv#18318))
- Add riscv64 musl target to build-release-binaries workflow ([#&#8203;18228](astral-sh/uv#18228))
- Fetch Ruff from an Astral mirror ([#&#8203;18286](astral-sh/uv#18286))
- Improve error handling for platform detection in Python downloads ([#&#8203;18453](astral-sh/uv#18453))
- Warn if `--project` directory does not exist ([#&#8203;17714](astral-sh/uv#17714))
- Warn when workspace member scripts are skipped due to missing build system ([#&#8203;18389](astral-sh/uv#18389))
- Update build backend versions used in `uv init` ([#&#8203;18417](astral-sh/uv#18417))
- Log explicit config file path in verbose output ([#&#8203;18353](astral-sh/uv#18353))
- Make `uv cache clear` an alias of `uv cache clean` ([#&#8203;18420](astral-sh/uv#18420))
- Reject invalid classifiers, warn on license classifiers in `uv_build` ([#&#8203;18419](astral-sh/uv#18419))

##### Preview features

- Add links to `uv audit` output ([#&#8203;18392](astral-sh/uv#18392))
- Output/report formatting for `uv audit` ([#&#8203;18193](astral-sh/uv#18193))
- Switch to batched OSV queries for `uv audit` ([#&#8203;18394](astral-sh/uv#18394))

##### Bug fixes

- Avoid sharing version metadata across indexes ([#&#8203;18373](astral-sh/uv#18373))
- Bump zlib-rs to 0.6.2 to fix panic on decompression of large wheels on Windows ([#&#8203;18362](astral-sh/uv#18362))
- Filter out unsupported environment wheels ([#&#8203;18445](astral-sh/uv#18445))
- Preserve absolute/relative paths in lockfiles ([#&#8203;18176](astral-sh/uv#18176))
- Recreate Python environments under `uv tool install --force` ([#&#8203;18399](astral-sh/uv#18399))
- Respect timestamp and other cache keys in cached environments ([#&#8203;18396](astral-sh/uv#18396))
- Simplify selected extra markers in `uv export` ([#&#8203;18433](astral-sh/uv#18433))
- Send pyx mint-token requests with a proper `Content-Type` ([#&#8203;18334](astral-sh/uv#18334))
- Fix Windows operating system and version reporting ([#&#8203;18383](astral-sh/uv#18383))

##### Documentation

- Update the platform support policy with a tier 3 section including freebsd and 32-bit windows ([#&#8203;18345](astral-sh/uv#18345))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My45Mi4xIiwidXBkYXRlZEluVmVyIjoiNDMuMTAyLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptaW5vciJdfQ==-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or improvement to existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants