fix(uv): prefer uv.lock versions in parser output#15074
Conversation
When both uv.lock and a requirements.txt/.in file list the same package, the parser previously returned the requirements.txt version (because DependencySet prefers a top_level? entry, and uv.lock entries have empty requirements). That caused stale versions to be submitted to the dependency graph and blocked Dependabot security alerts from auto-dismissing after a uv lock had already shipped the patched version. PR #14954 attempted to skip requirement_dependencies entirely when uv.lock was present, but that stripped requirements from deps that the FileUpdater needs to update, surfacing as a spike in 'Expected lockfile to change!' errors in LockFileUpdater#fetch_updated_dependency_files. Instead, build the full dependency set as before, then post-process it so any package that also appears in uv.lock takes the lockfile-resolved version while keeping its requirements (so the FileUpdater can still operate on those files). Lift the lockfile parsing into a dedicated LockFileDependencyParser helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes uv dependency parsing so resolved versions from uv.lock are preferred over stale versions from requirements files while preserving requirements metadata for file updates.
Changes:
- Adds a nested
LockFileDependencyParserhelper for parsinguv.lock. - Post-processes parsed dependencies to replace versions with lockfile-resolved versions when available.
- Adds specs covering stale requirements files and requirements-only packages.
Show a summary per file
| File | Description |
|---|---|
uv/lib/dependabot/uv/file_parser.rb |
Wires in the lockfile parser and applies lockfile version preference after building the full dependency set. |
uv/lib/dependabot/uv/file_parser/lock_file_dependency_parser.rb |
Adds uv.lock parsing and dependency version preference logic. |
uv/spec/dependabot/uv/file_parser_spec.rb |
Adds regression coverage for stale requirements versions and requirements preservation. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 0
Rename prefer_lockfile_versions -> override_with_lockfile_versions and split out override_version so the two early-return cases (not-in-lockfile, version already correct) are obvious. Add a comment on the public method explaining why we override after DependencySet has merged, not before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
brrygrdn
left a comment
There was a problem hiding this comment.
Nice, this makes sense to me from a dependency graphing perspective. Because it affects updates as well I'll hold off on giving a formal ✅ as it's probably worth a second pair of eyes
|
Wait, why is the uv ecosystem fetching and parsing requirements.txt and .in files? |
Yeah good question - customers can have both in their repo and uv lets you export lockfiles to requirements.txt https://docs.astral.sh/uv/concepts/projects/export/#requirementstxt-format But we can also go the route of ignoring requirements.txt for uv graph jobs. I'll take a look at that option. |
What are you trying to accomplish?
Fix the underlying bug from #14954 (which I had to roll back because it broke the file updater). Credit to @jmcgrath-tractian for the original report and fixture.
When both
uv.lockand arequirements.txt/.infile list the same package,Dependabot::Uv::FileParser#parsepreviously returned therequirements.txtversion.DependencySet#combined_versionprefers the entry whosetop_level?istrue, anduv.lockentries always have emptyrequirements: [], so therequirements.txtentry wins — even when it is stale.Result: the dependency graph reports the stale version, and Dependabot security alerts don't auto-dismiss after
uv lockhas already shipped the patched version.What changed
Follows @v-HaripriyaC's suggestion on #14954 to move the fix out of "skip the requirements parser entirely" and into dependency selection:
pyproject+uv.lock+requirements.*)uv.locktakes the lockfile-resolved version, while keeping the requirements collected from other filesuv.lockparsing into a dedicatedFileParser::LockFileDependencyParserhelperNet effect:
uv.lockversionFileUpdaterstill sees therequirements.txtentry indep.requirements, so it can update those files when needed — no spike inExpected lockfile to change!fromLockFileUpdater#fetch_updated_dependency_filesrequirements.txt(nouv.lockentry) are unaffectedTests
Three new specs in
uv/spec/dependabot/uv/file_parser_spec.rb:requirements.txt+uv.lock→ parser returns theuv.lockversionrequirements.txtis still listed in the dep'srequirements(so the file updater path still works)requirements.txt(not inuv.lock) → still parsed normallyVerified all 70
file_parser_specexamples plusdependency_grapher_specandfile_updater_specpass. The one workspace-member failure that shows up is pre-existing onmain.Checklist