Skip to content

Add experimental uv resolver for Python lockfiles#22949

Open
Liam-Deacon wants to merge 5 commits intopantsbuild:mainfrom
Liam-Deacon:uv-lockfile-resolver
Open

Add experimental uv resolver for Python lockfiles#22949
Liam-Deacon wants to merge 5 commits intopantsbuild:mainfrom
Liam-Deacon:uv-lockfile-resolver

Conversation

@Liam-Deacon
Copy link
Contributor

@Liam-Deacon Liam-Deacon commented Dec 16, 2025

Summary

This PR adds an experimental uv-backed resolver option for Python user lockfile generation.

Pants still generates and consumes Pex lockfiles, but when opted in it uses uv pip compile to pre-resolve pins, then uses pex lock create --no-transitive to materialize the Pex lock.

Capability Before After (opt-in)
Lockfile generator Pex (pex lock create) still Pex
Resolver used for the solve step pip uv (PubGrub)
Extra flags pip/pex flags only uv flags via [uv].args

Motivation

Issue: #20679 — faster, more ergonomic lockfile generation by leveraging uv.

How it works

requirements (targets)
        |
        |  (opt-in) uv pip compile
        v
 pinned requirements.txt
        |
        |  pex lock create --no-transitive
        v
     Pex lockfile

Usage

1) Enable resolves and select the uv resolver

[python]
enable_resolves = true
lockfile_resolver = "uv"  # <-- opt in

# uv currently requires a single Python major/minor
interpreter_constraints = ["CPython==3.11.*"]

[python.resolves]
python-default = "3rdparty/python/default.lock"

# uv is currently supported for strict/sources, not universal
[python.resolves_to_lock_style]
python-default = "strict"

2) Pass arbitrary uv flags (passthrough)

Use the standard args pattern (same as other downloadable tools in Pants):

[uv]
# Example requested in the issue: prefer the first matching index.
args = ["--index-strategy", "unsafe-first-match"]

Equivalent CLI:

pants generate-lockfiles --resolve=python-default \
  --python-lockfile-resolver=uv \
  --uv-args='--index-strategy unsafe-first-match'

Current limitations (intentional safeguards)

Limitation Reason
No lock_style = "universal" uv pip compile resolves for a single interpreter environment today
No complete_platforms not wired through in this initial integration
Interpreter constraints must select one Python major/minor ensures uv resolves for exactly one target interpreter
Per-resolve sources / overrides / excludes not yet modeled in uv step avoids silently changing semantics

Benchmark (real-world example)

Environment:

  • Python: CPython==3.11.*
  • Lock style: strict
  • Resolver modes compared: pip vs uv
  • Input: annotated + sorted concat of:
    • ~/repos/python-mono/3rdparty/python/requirements.txt
    • ~/repos/python-mono/3rdparty/python/requirements-dev.txt
  • Excluded from the benchmark input to keep it public/reproducible:
    • any pip.antarcticaam.app/private references
    • blpapi, StressVaR, module-wrapper, pytest-localstack

Timing (/usr/bin/time -p, Pantsd disabled; repeated to show warm-cache behavior):

Method:

  • Buildroot: /tmp/pants-uv-lockfile-benchmark (isolated), using python_requirements(...) pointing at the filtered requirements.in.
  • Each run uses a fresh PANTS_WORKDIR to avoid Pants's local process cache reusing results.
  • Tool caches persist across repeats:
    • PANTS_GLOBAL_PEX_ROOT=/tmp/pants-uv-lockfile-benchmark/cache/pex_root
    • PIP_CACHE_DIR=/tmp/pants-uv-lockfile-benchmark/cache/pip_cache
    • UV_CACHE_DIR=/tmp/pants-uv-lockfile-benchmark/cache/uv_cache
  • “cold” = cache dirs removed before the run; “warm” = immediate repeat with caches retained.
Mode Run real (s) user (s) sys (s)
pip cold 323.58 144.50 40.03
pip warm (repeat) 117.52 136.15 29.27
uv cold 90.49 108.48 29.05
uv warm (repeat) 101.41 121.51 32.02

Speedup (lower is better):

  • Cold: pip/uv = 3.58x
  • Warm: pip/uv = 1.16x

Notes:

  • Results vary with network / platform and the resolver's cache state. The intent is to show that the uv pre-solve materially reduces cold-cache solve time in this real-world-ish set.

Additional visibility (uv mode only):

Phase Observed duration
uv pip compile pre-solve (previous run-set) 25.39s

In this run-set, uv reduced end-to-end wall time vs pip (especially cold-cache). Warm-cache repeats narrow the gap, which suggests resolver overhead is a larger fraction of first-time generation than subsequent runs.

Code changes

Area Change
src/python/pants/backend/python/subsystems/setup.py Adds [python].lockfile_resolver = {pip,uv}
src/python/pants/backend/python/subsystems/uv.py Adds hermetic uv tool + [uv].args passthrough
src/python/pants/backend/python/goals/lockfile.py Runs uv pip compile pre-step (opt-in) then pex lock create --no-transitive
src/python/pants/backend/python/goals/lockfile_test.py Adds unit tests for uv-mode validation behavior
docs/docs/python/overview/lockfiles.mdx Documents uv resolver configuration and flags

@jasonwbarnett
Copy link
Contributor

Wow, this is a really creative design 🤩

@cburroughs
Copy link
Contributor

Thanks for the contribution. We've just branched for 2.31.x, so merging this pull request now will come out in 2.32.x, please move the release notes updates to docs/notes/2.32.x.md if that's appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants