豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Add uv dependency grapher#14295

Merged
Nishnha merged 1 commit intomainfrom
uv-dependency-grapher
Feb 27, 2026
Merged

Add uv dependency grapher#14295
Nishnha merged 1 commit intomainfrom
uv-dependency-grapher

Conversation

@Nishnha
Copy link
Copy Markdown
Member

@Nishnha Nishnha commented Feb 26, 2026

What are you trying to accomplish?

Add a dependency grapher for the uv package manager.

Anything you want to highlight for special attention from reviewers?

This expects at least a pyproject.toml. If a uv.lock is not present an ephemeral one is generated.

The dependency tree is parsed from the lockfile, ephemeral lockfile, or uv tree command if the lockfile parsing fails.

How will you know you've accomplished your goal?

uv projects send dependency graph information to the dependency graph

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

@Nishnha
Copy link
Copy Markdown
Member Author

Nishnha commented Feb 27, 2026

Built the branch locally with script/build uv and then ran it against the cli on a local test repo with dependabot graph uv test/repo --local /workspaces/test --directory / --branch main --updater-image "ghcr.io/dependabot/dependabot-updater-uv"

It ran successfully! Check out the output below

Details
    cli | 2026/02/27 16:58:54 Using hostname: github.com api endpoint: https://api.github.com
    cli | 2026/02/27 16:58:54 Inserting $LOCAL_GITHUB_ACCESS_TOKEN into credentials
    cli | 2026/02/27 16:58:55 image ghcr.io/dependabot/proxy:latest is already up to date
    cli | 2026/02/27 16:58:55 using image ghcr.io/dependabot/proxy:latest at sha256:679cbf3063e3040cc862626ae33b1c02518a02357b1f2d19e2de30cb363b6709
  proxy | 2026/02/27 16:58:55 proxy starting, commit: 4e40087dd12ab05331bbf4f5a6618fc0c127de07
  proxy | 2026/02/27 16:58:55 Listening (:1080)
updater | Reinitialized existing Git repository in /home/dependabot/dependabot-updater/repo/.git/
updater | Updating certificates in /etc/ssl/certs...
updater | rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
updater | 1 added, 0 removed; done.
updater | Running hooks in /etc/ca-certificates/update.d...
updater | done.
updater | fetch_files command is no longer used directly
updater | 2026/02/27 16:58:58 INFO Starting job processing
updater | 2026/02/27 16:58:58 INFO Job definition: {"job":{"command":"graph","package-manager":"uv","allowed-updates":[{"update-type":"all"}],"debug":false,"dependency-groups":[],"dependencies":null,"dependency-group-to-refresh":null,"existing-pull-requests":[],"existing-group-pull-requests":[],"experiments":{"enable_dependency_submission_poc":true},"ignore-conditions":[],"lockfile-only":false,"requirements-update-strategy":null,"security-advisories":[],"security-updates-only":false,"source":{"provider":"github","repo":"test/repo","directories":["/"],"branch":"main","hostname":"github.com","api-endpoint":"https://api.github.com"},"update-subdependencies":false,"updating-a-pull-request":false,"vendor-dependencies":false,"reject-external-code":false,"repo-private":false,"commit-message-options":null,"credentials-metadata":[{"host":"github.com","type":"git_source"}],"max-updater-run-time":0,"exclude-paths":null,"multi-ecosystem-update":false}}
  proxy | 2026/02/27 16:58:58 [002] GET https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:58 [002] * authenticating git server request (host: github.com)
  proxy | 2026/02/27 16:58:58 [002] 401 https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:58 [002] Remote response: Invalid username or token. Password authentication is not supported for Git operations.
  proxy | 2026/02/27 16:58:58 [004] GET https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:58 [004] * authenticating git server request (host: github.com)
  proxy | 2026/02/27 16:58:58 [004] 401 https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:58 [004] Remote response: Invalid username or token. Password authentication is not supported for Git operations.
  proxy | 2026/02/27 16:58:58 [004] * auth'd git request previously retried, won't retry again.
updater | 2026/02/27 16:58:58 WARN Could not validate target branch early due to git metadata fetch error: The following git URLs could not be retrieved: https://github.com/test/repo
  proxy | 2026/02/27 16:58:59 [006] GET https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:59 [006] 401 https://github.com:443/test/repo.git/info/refs?service=git-upload-pack (cached)
  proxy | 2026/02/27 16:58:59 [006] Remote response: Invalid username or token. Password authentication is not supported for Git operations. (cached)
  proxy | 2026/02/27 16:58:59 [006] * auth'd git request previously retried, won't retry again. (cached)
  proxy | 2026/02/27 16:58:59 [008] GET https://github.com:443/test/repo.git/info/refs?service=git-upload-pack
  proxy | 2026/02/27 16:58:59 [008] 401 https://github.com:443/test/repo.git/info/refs?service=git-upload-pack (cached)
  proxy | 2026/02/27 16:58:59 [008] Remote response: Invalid username or token. Password authentication is not supported for Git operations. (cached)
  proxy | 2026/02/27 16:58:59 [008] * auth'd git request previously retried, won't retry again. (cached)
updater | 2026/02/27 16:58:59 WARN Could not validate the existence of the 'dependabot' branch: The following git URLs could not be retrieved: https://github.com/test/repo
updater | 2026/02/27 16:58:59 INFO Base commit SHA: aa063b1372c20a91aa71e5ac5bc9aa98cb443449
updater | 2026/02/27 16:58:59 INFO Finished job processing
updater | 2026/02/27 16:58:59 INFO Starting job processing
updater | 2026/02/27 16:59:03 INFO Dependency submission payload:
updater | {
updater |   "version": 1,
updater |   "sha": "aa063b1372c20a91aa71e5ac5bc9aa98cb443449",
updater |   "ref": "refs/heads/main",
updater |   "job": {
updater |     "correlator": "dependabot-uv",
updater |     "id": "cli"
updater |   },
updater |   "detector": {
updater |     "name": "dependabot",
updater |     "version": "0.363.0",
updater |     "url": "https://github.com/dependabot/dependabot-core"
updater |   },
updater |   "manifests": {
updater |     "/pyproject.toml": {
updater |       "name": "/pyproject.toml",
updater |       "file": {
updater |         "source_location": "pyproject.toml"
updater |       },
updater |       "metadata": {
updater |         "ecosystem": "pypi"
updater |       },
updater |       "resolved": {
updater |         "pkg:pypi/flask@3.1.3": {
updater |           "package_url": "pkg:pypi/flask@3.1.3",
updater |           "relationship": "direct",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/blinker@1.9.0",
updater |             "pkg:pypi/click@8.3.1",
updater |             "pkg:pypi/itsdangerous@2.2.0",
updater |             "pkg:pypi/jinja2@3.1.6",
updater |             "pkg:pypi/markupsafe@3.0.3",
updater |             "pkg:pypi/werkzeug@3.1.6"
updater |           ]
updater |         },
updater |         "pkg:pypi/requests@2.32.5": {
updater |           "package_url": "pkg:pypi/requests@2.32.5",
updater |           "relationship": "direct",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/certifi@2026.2.25",
updater |             "pkg:pypi/charset-normalizer@3.4.4",
updater |             "pkg:pypi/idna@3.11",
updater |             "pkg:pypi/urllib3@2.6.3"
updater |           ]
updater |         },
updater |         "pkg:pypi/ruff@0.15.4": {
updater |           "package_url": "pkg:pypi/ruff@0.15.4",
updater |           "relationship": "direct",
updater |           "scope": "development",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/ty@0.0.19": {
updater |           "package_url": "pkg:pypi/ty@0.0.19",
updater |           "relationship": "direct",
updater |           "scope": "development",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/blinker@1.9.0": {
updater |           "package_url": "pkg:pypi/blinker@1.9.0",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/certifi@2026.2.25": {
updater |           "package_url": "pkg:pypi/certifi@2026.2.25",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/charset-normalizer@3.4.4": {
updater |           "package_url": "pkg:pypi/charset-normalizer@3.4.4",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/click@8.3.1": {
updater |           "package_url": "pkg:pypi/click@8.3.1",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/colorama@0.4.6"
updater |           ]
updater |         },
updater |         "pkg:pypi/colorama@0.4.6": {
updater |           "package_url": "pkg:pypi/colorama@0.4.6",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/idna@3.11": {
updater |           "package_url": "pkg:pypi/idna@3.11",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/itsdangerous@2.2.0": {
updater |           "package_url": "pkg:pypi/itsdangerous@2.2.0",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/jinja2@3.1.6": {
updater |           "package_url": "pkg:pypi/jinja2@3.1.6",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/markupsafe@3.0.3"
updater |           ]
updater |         },
updater |         "pkg:pypi/markupsafe@3.0.3": {
updater |           "package_url": "pkg:pypi/markupsafe@3.0.3",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/test@0.1.0": {
updater |           "package_url": "pkg:pypi/test@0.1.0",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/flask@3.1.3",
updater |             "pkg:pypi/requests@2.32.5"
updater |           ]
updater |         },
updater |         "pkg:pypi/urllib3@2.6.3": {
updater |           "package_url": "pkg:pypi/urllib3@2.6.3",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": []
updater |         },
updater |         "pkg:pypi/werkzeug@3.1.6": {
updater |           "package_url": "pkg:pypi/werkzeug@3.1.6",
updater |           "relationship": "indirect",
updater |           "scope": "runtime",
updater |           "dependencies": [
updater |             "pkg:pypi/markupsafe@3.0.3"
updater |           ]
updater |         }
updater |       }
updater |     }
updater |   },
updater |   "metadata": {
updater |     "status": "ok"
updater |   }
updater | }
  proxy | 2026/02/27 16:59:03 [009] POST http://host.docker.internal:43393/update_jobs/cli/create_dependency_submission
{"data":{"version":1,"sha":"aa063b1372c20a91aa71e5ac5bc9aa98cb443449","ref":"refs/heads/main","job":{"correlator":"dependabot-uv","id":"cli"},"detector":{"name":"dependabot","url":"https://github.com/dependabot/dependabot-core","version":"0.363.0"},"manifests":{"/pyproject.toml":{"file":{"source_location":"pyproject.toml"},"metadata":{"ecosystem":"pypi"},"name":"/pyproject.toml","resolved":{"pkg:pypi/blinker@1.9.0":{"dependencies":[],"package_url":"pkg:pypi/blinker@1.9.0","relationship":"indirect","scope":"runtime"},"pkg:pypi/certifi@2026.2.25":{"dependencies":[],"package_url":"pkg:pypi/certifi@2026.2.25","relationship":"indirect","scope":"runtime"},"pkg:pypi/charset-normalizer@3.4.4":{"dependencies":[],"package_url":"pkg:pypi/charset-normalizer@3.4.4","relationship":"indirect","scope":"runtime"},"pkg:pypi/click@8.3.1":{"dependencies":["pkg:pypi/colorama@0.4.6"],"package_url":"pkg:pypi/click@8.3.1","relationship":"indirect","scope":"runtime"},"pkg:pypi/colorama@0.4.6":{"dependencies":[],"package_url":"pkg:pypi/colorama@0.4.6","relationship":"indirect","scope":"runtime"},"pkg:pypi/flask@3.1.3":{"dependencies":["pkg:pypi/blinker@1.9.0","pkg:pypi/click@8.3.1","pkg:pypi/itsdangerous@2.2.0","pkg:pypi/jinja2@3.1.6","pkg:pypi/markupsafe@3.0.3","pkg:pypi/werkzeug@3.1.6"],"package_url":"pkg:pypi/flask@3.1.3","relationship":"direct","scope":"runtime"},"pkg:pypi/idna@3.11":{"dependencies":[],"package_url":"pkg:pypi/idna@3.11","relationship":"indirect","scope":"runtime"},"pkg:pypi/itsdangerous@2.2.0":{"dependencies":[],"package_url":"pkg:pypi/itsdangerous@2.2.0","relationship":"indirect","scope":"runtime"},"pkg:pypi/jinja2@3.1.6":{"dependencies":["pkg:pypi/markupsafe@3.0.3"],"package_url":"pkg:pypi/jinja2@3.1.6","relationship":"indirect","scope":"runtime"},"pkg:pypi/markupsafe@3.0.3":{"dependencies":[],"package_url":"pkg:pypi/markupsafe@3.0.3","relationship":"indirect","scope":"runtime"},"pkg:pypi/requests@2.32.5":{"dependencies":["pkg:pypi/certifi@2026.2.25","pkg:pypi/charset-normalizer@3.4.4","pkg:pypi/idna@3.11","pkg:pypi/urllib3@2.6.3"],"package_url":"pkg:pypi/requests@2.32.5","relationship":"direct","scope":"runtime"},"pkg:pypi/ruff@0.15.4":{"dependencies":[],"package_url":"pkg:pypi/ruff@0.15.4","relationship":"direct","scope":"development"},"pkg:pypi/test@0.1.0":{"dependencies":["pkg:pypi/flask@3.1.3","pkg:pypi/requests@2.32.5"],"package_url":"pkg:pypi/test@0.1.0","relationship":"indirect","scope":"runtime"},"pkg:pypi/ty@0.0.19":{"dependencies":[],"package_url":"pkg:pypi/ty@0.0.19","relationship":"direct","scope":"development"},"pkg:pypi/urllib3@2.6.3":{"dependencies":[],"package_url":"pkg:pypi/urllib3@2.6.3","relationship":"indirect","scope":"runtime"},"pkg:pypi/werkzeug@3.1.6":{"dependencies":["pkg:pypi/markupsafe@3.0.3"],"package_url":"pkg:pypi/werkzeug@3.1.6","relationship":"indirect","scope":"runtime"}}}},"metadata":{"status":"ok"}},"type":"create_dependency_submission"}
  proxy | 2026/02/27 16:59:03 [009] 200 http://host.docker.internal:43393/update_jobs/cli/create_dependency_submission
  proxy | 2026/02/27 16:59:03 [010] PATCH http://host.docker.internal:43393/update_jobs/cli/mark_as_processed
{"data":{"base-commit-sha":"aa063b1372c20a91aa71e5ac5bc9aa98cb443449"},"type":"mark_as_processed"}
  proxy | 2026/02/27 16:59:03 [010] 200 http://host.docker.internal:43393/update_jobs/cli/mark_as_processed
updater | 2026/02/27 16:59:03 INFO Finished job processing
  proxy | 2026/02/27 16:59:04 Skipping sending metrics because api endpoint is empty
  proxy | 2026/02/27 16:59:04 2/4 calls cached (50%)

@Nishnha Nishnha force-pushed the uv-dependency-grapher branch from f41dfd5 to f74818f Compare February 27, 2026 17:09
@Nishnha Nishnha marked this pull request as ready for review February 27, 2026 17:09
@Nishnha Nishnha requested a review from a team as a code owner February 27, 2026 17:09
Copilot AI review requested due to automatic review settings February 27, 2026 17:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds a dependency grapher for the uv package manager ecosystem, enabling Dependabot to generate dependency graph information for UV projects. The implementation follows established patterns from other ecosystems (go_modules, npm_and_yarn) and provides a robust fallback strategy for parsing dependency relationships.

Changes:

  • Adds DependencyGrapher class that extracts dependency relationships from uv.lock, generates ephemeral lockfiles when needed, or falls back to parsing uv tree output
  • Extends FileParser with run_in_parsed_context method to execute native UV commands in the project context
  • Includes comprehensive test coverage for all three relationship extraction strategies (lockfile, generated lockfile, tree command)

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
uv/lib/dependabot/uv/dependency_grapher.rb Main implementation: parses dependency relationships from lockfile or tree output with fallback strategies
uv/lib/dependabot/uv/file_parser.rb Adds run_in_parsed_context method to execute commands in temporary directory with dependency files
uv/lib/dependabot/uv.rb Registers the new dependency grapher component
uv/spec/dependabot/uv/dependency_grapher_spec.rb Comprehensive test suite covering all relationship extraction paths and edge cases
uv/spec/fixtures/* Test fixtures for lockfiles and tree output

@Nishnha
Copy link
Copy Markdown
Member Author

Nishnha commented Feb 27, 2026

The package urls all say pypi instead of uv. Not sure if that's what DG expects?

Comment on lines +14 to +15
UV_LOCK_COMMAND = T.let("pyenv exec uv lock --color never --no-progress && cat uv.lock", String)
UV_TREE_COMMAND = T.let("pyenv exec uv tree -q --color never --no-progress --frozen", String)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not something that needs to be addressed as part of this PR - I'm not super familiar with which uv commands access remote registries so I wonder if there are private dependencies involved will either of these commands fail if the credentials haven't been configured?

With golang, we have some handling for go mod graph iirc as it could fail without creds but we could fall back to getting a snapshot of the dependencies without subdependencies.

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 should work as is, I built the fallback into the Base DependencyGrapher. So if any error occurs we'll still post the high level data from the FileParser.

Copy link
Copy Markdown
Member Author

@Nishnha Nishnha Feb 27, 2026

Choose a reason for hiding this comment

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

Good spot. I'll investigate this in a follow up. DG isn't sending graph job requests for uv yet, so this should be safe to merge even if it is broken for private dependencies.

Copy link
Copy Markdown
Contributor

@brrygrdn brrygrdn left a comment

Choose a reason for hiding this comment

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

👍🏻 LGTM, one question but its non-blocking

@Nishnha Nishnha force-pushed the uv-dependency-grapher branch from f74818f to 9d097d4 Compare February 27, 2026 19:27
@Nishnha Nishnha merged commit c332992 into main Feb 27, 2026
83 checks passed
@Nishnha Nishnha deleted the uv-dependency-grapher branch February 27, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants