Skip to content

feat(projectgraph): link Python dependency code, read its type hints, and add the distribution graph - #55

Open
abhisek wants to merge 12 commits into
mainfrom
claude/python-call-graph-plan-vakrcz
Open

abhisek wants to merge 12 commits into
mainfrom
claude/python-call-graph-plan-vakrcz

Conversation

@abhisek

@abhisek abhisek commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Summary

The whole-project call graph links Python dependency code, but Python had none of the second layer that #51 to #53 gave Go and Rust: a measure of how often a method target of a dependency ends uncertain, external keys for the definitions of a unit, source rules for the files of a unit, declared types for the receivers, and a graph of which dependency imports which. This pull request is the whole Python plan on the code side: the measure, the pypi unit, the type hints, the distribution graph and the standard library unit.

Step 1: the measure

  • receivers_test.go has 18 Python rows against a pypi unit under testdata/receivers/python.
  • The method corpus has 10 Python projects with 14 pinned dependencies. OSV lists no symbols for a PyPI advisory, so scripts/pypi_advisories.py holds the symbols of 72 advisories by hand and pulls the ranges from OSV. fetch_method_corpus.sh unpacks a dependency from its wheel and a project from its sdist. PROJECTGRAPH_METHOD_DETAIL writes one line per target with its outcome and sites.

Step 2: four rules that the baseline forced, and the pypi unit

  1. An incomplete file counts for a search only when the program runs its language. Graph.Runs decides, from Node.Language and Stats.Languages.
  2. An incomplete test file counts only for a search that includes the tests.
  3. A decorator is a dynamic feature only when the graph cannot name its code, as @handlers[name]. A call of the value of a call, as click.option("-v")(f), is no computed-name record, unless the inner call reflects.
  4. A Python import whose first segment is no module under an import root or in the directory of the importer is external.
  • modules.PythonDistributions reads the .dist-info at the root of a unit: the import names, the files of RECORD, its extension modules and its Requires-Dist lines. An import of an extension module is a record with the reason no-dependency-source.
  • The Python profile gets the symbol rule, so a definition of a unit carries the key that an importer writes, as requests//api//get.
  • The index step leaves out the test files and the wheel metadata of a pypi unit.
  • examples/projectgraph takes -site-packages <dir>.

Step 3: the type hints

  • The index records the class of a parameter hint, with Optional, Union and | None unwrapped, the return type and the tuple positions of a function, an annotated local, a name that holds a call result, a typed name of the top level of a module, and the fields of a class from its annotations and from the attributes that its methods set.
  • A call on a typed name gets the key of the name in its scope as its target, as the Go index does, so the link step reads the type. A result of a reflective call, as import_module, has no type.
  • The link step trusts the hints as it trusts TypeScript types. A hint of a dependency class gives the external key of the method, the class hierarchy analysis adds the overrides in the project and in the unit, and an abstract method with no override is a record with the reason interface-receiver.
  • All 16 Python receiver rows that waited on this step give their wanted answer. Six cases cover the forms.

Step 5: the distribution graph

distgraph builds the graph of which installed distribution imports which, from the .dist-info directories of each site-packages directory and the import statements of the Python files, with no Python. It follows the rule of modgraph: it never misses an edge that a run has. A distribution with an extension module, a computed import, an import that nothing holds, a file that does not parse, or a file past the budget is open. An import in a try statement that catches an ImportError opens nothing. examples/distgraph prints the graph, and docs/distgraph.md holds the rules.

Step 6: the standard library unit

The Lib directory of CPython is the unit python:std@<version>, as GOROOT/src is go:std. The index step leaves out its test suite, the IDLE, the turtle demos and lib2to3. A class of a unit gives its external key to the class hierarchy analysis, so Thread.run in the unit reaches the run of a project class with a cha edge.

Numbers

Run Targets found found/low unknown-receiver ambiguous computed-name dynamic-flag no-definition budget
Baseline 107 5 0 30 0 0 19 6 47
Four rules 107 5 0 30 28 12 19 8 5
Step 2 107 1 54 4 21 6 16 1 4
Step 3 107 2 52 4 22 6 16 1 4

The corpus moves little in step 3 because its paths cross requests, Django 4.2 and urllib3 1.26, which have no type hints on the receivers that the paths cross. The receiver rows and the cases prove the mechanism. One false found/low path through a name edge in Django becomes ambiguous. pycg-micro keeps 0.954 precision and 0.708 recall. govulncheck stays at 9 of 9 stacks. report.md has the tables and the causes of each step.

Versions: fileindex.Version 15, projectgraph.Version 17, reach.Version 4.

Verification

go test ./..., go vet ./..., go vet -tags projectgraph ./test/projectgraph/, golangci-lint run and scripts/check_projectgraph_gates.sh pass. TestBuildHoldsEachEdgeOfGoList of modgraph fails in the development container on main too, because the module cache there lacks the source of two modules.


Generated by Claude Code

The Python plan starts with a number. The link step reads no type hint
of Python, and no Python project is in the method corpus, so nobody
could say how often a Python method target ends uncertain.

receivers_test.go has 18 Python rows against a pypi unit under
testdata/receivers/python. Two rows give their answer today. The other
16 name the type-hint step of the plan and their answer before it. A
call on a variable that holds the result of an external call gets the
false key dep//new_thing//do and no record, so the method of the real
type is no-such-target.

The method corpus has 10 Python projects with 14 pinned dependencies.
OSV lists no symbols for a PyPI advisory, so scripts/pypi_advisories.py
holds the symbols of 72 advisories by hand, as the advisory text names
them, and writes them with the ranges from OSV. fetch_method_corpus.sh
unpacks a dependency from its wheel, so the unit has the layout of
site-packages, and a project from its sdist. methods_test.go reads the
pypi ecosystem and takes PROJECTGRAPH_METHOD_LANGUAGES to index one
language.

report.md has the baseline: 5 of 107 targets found, none not-found and
none no-such-target. Every search meets an uncertainty that concerns
every target. The causes are the bundled JavaScript that the index
truncates, the decorator-wrap flag on every framework decorator, a
truncated test file, and the unknown receiver of 27 of 63 methods.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
…s that the program runs

The Python baseline of the method measure had every target uncertain.
Three causes hid the receiver forms that the type-hint step must fix, and
none of them was a call that the graph could not see.

A Python project ships the JavaScript of its web pages, and the index
step truncates a minified file of it. Every search counted the file as a
budget. The index step now reports the language of each file, the link
step keeps it on the module node, and Stats.Languages holds the languages
of the files with a root other than a test root. Graph.Runs reads them,
with the languages that the ecosystem of a unit installs. The reach query
and the summary leave out the budget of a file that the program does not
run. A truncated test file counts only for a search that includes the
tests.

The index flagged every decorator that was not a bare name as
decorator-wrap, so each click or Flask project made every target
uncertain. A decorator that is a name, a dotted name or a call of one is
a call of the scope that defines the function, which the link step
reads. The flag stays for a decorator that the graph cannot name, as
@handlers[name]. A call of the value of a call, as click.option(...)(f),
is no computed-name record of its own, unless the inner call reflects.

An absolute Python import was first party when a file of the name was
anywhere in the project, so import types in cryptography and import
collections in Django were records that concerned every target. The
first segment must now be a module under an import root or in the
directory of the importer, as the import system reads it.

Five cases cover the rules. flask-route now reads a dead call as
not-found. pycg-micro keeps 0.954 precision and 0.708 recall, with the
unresolved rate down from 0.340 to 0.312. The Python budget of the
measure falls from 47 to 5 targets, and the run of every language gives
the same numbers as the run of Python alone. report.md has the table.

fileindex.Version 15, projectgraph.Version 17, reach.Version 4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
…rules commit

The rules commit went out with three tests red: the module-getattr line
of the Python flag fixture moved with the new decorator, the index
goldens had no hash row for fileindex.Version 15, and the golden graph of
a project with test roots alone still listed python as a program
language. go test ./... passes with exit 0 now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
…efinitions external keys

A pypi unit knew only the files that the index step read. The link step
named its import name from the package name, so PyYAML gave no yaml, an
extension module was an external edge that hid its code, every test file
of the package was in the graph, and a target that only the code of the
package called matched nothing.

modules.PythonDistributions reads the .dist-info directories at the root
of a unit, as a site-packages directory or an unpacked wheel holds them:
the name and the version of METADATA, the import names of top_level.txt
or RECORD, the files of RECORD and its extension modules. The link step
matches the one of the unit by the normalized name and gives the Python
resolver its import names and its extension modules. An import of an
extension module is a record with the reason no-dependency-source. The
Python profile gets the symbol rule that Go and Rust have, so each
definition of a unit carries the key that an importer of its module
writes. The index step leaves out the test files and the wheel metadata
of a pypi unit, as it leaves out the test files of a Go unit.

examples/projectgraph takes -site-packages dir and adds each distribution
of the directory as a unit, by the roots that its RECORD names, so a
namespace package that two distributions share and a single-file module
both get their units.

The measure and the receiver rows say found/low for a path with a hop of
low confidence. The external keys move 54 of 107 Python targets from
uncertain to found/low, and unknown-receiver falls from 27 to 4 of 63
methods. report.md has the table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
@safedep

safedep Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

No dependency changes detected. Nothing to scan.

View complete scan results →

This report is generated by SafeDep GitHub App

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 249 files, which is 149 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Check out review usage here.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: cb04515e-e117-4ad7-ba20-26f9bebfa05f
Commits

Reviewing files that changed from the base of the PR and between c270ee9 and 618ad61.

Files ignored due to path filters: 1
  • test/projectgraph/cases/python/bundled-javascript/project/static/vendor.min.js is excluded by !**/*.min.js
Files selected for processing: 249
  • README.md
  • distgraph/files.go
  • distgraph/graph.go
  • distgraph/graph_test.go
  • distgraph/imports.go
  • distgraph/imports_test.go
  • distgraph/stdlib.go
  • docs/distgraph.md
  • docs/projectgraph.md
  • examples/distgraph/main.go
  • examples/distgraph/main_test.go
  • examples/projectgraph/main.go
  • examples/projectgraph/main_test.go
  • fileindex/index.go
  • fileindex/testdata/index.json
  • plugin/callgraph/python.go
  • plugin/callgraph/records_test.go
  • plugin/indexer/calls.go
  • plugin/indexer/flags.go
  • plugin/indexer/flags_test.go
  • plugin/indexer/golang.go
  • plugin/indexer/indexer.go
  • plugin/indexer/languages.go
  • plugin/indexer/plugin_test.go
  • plugin/indexer/python.go
  • plugin/indexer/python_test.go
  • plugin/indexer/pyunit.go
  • plugin/indexer/pyunit_test.go
  • plugin/indexer/testdata/flags.py
  • plugin/indexer/testdata/golden/App.cs.json
  • plugin/indexer/testdata/golden/App.java.json
  • plugin/indexer/testdata/golden/App.kt.json
  • plugin/indexer/testdata/golden/app.go.json
  • plugin/indexer/testdata/golden/app.js.json
  • plugin/indexer/testdata/golden/app.php.json
  • plugin/indexer/testdata/golden/app.py.json
  • plugin/indexer/testdata/golden/app.rb.json
  • plugin/indexer/testdata/golden/app.rs.json
  • plugin/indexer/testdata/golden/app.ts.json
  • plugin/indexer/testdata/golden/exports.js.json
  • plugin/indexer/testdata/golden/exports.py.json
  • plugin/indexer/testdata/golden/exports.ts.json
  • plugin/indexer/testdata/golden/flags.js.json
  • plugin/indexer/testdata/golden/flags.py.json
  • plugin/indexer/testdata/golden/flags.ts.json
  • plugin/indexer/types_test.go
  • projectgraph/cha.go
  • projectgraph/declared.go
  • projectgraph/definitions.go
  • projectgraph/graph.go
  • projectgraph/imports.go
  • projectgraph/link.go
  • projectgraph/modules/pydist.go
  • projectgraph/modules/pydist_test.go
  • projectgraph/modules/python.go
  • projectgraph/modules/python_test.go
  • projectgraph/modules/resolver.go
  • projectgraph/packages.go
  • projectgraph/profile.go
  • projectgraph/python.go
  • projectgraph/reach/reach.go
  • projectgraph/reach/reach_test.go
  • projectgraph/reach/uncertainty.go
  • projectgraph/resolve.go
  • projectgraph/roots.go
  • projectgraph/summary/summary.go
  • projectgraph/testdata/javascript/barrel-chain/graph.golden.json
  • projectgraph/testdata/javascript/barrel-chain/indexes.json
  • projectgraph/testdata/javascript/commonjs-require/graph.golden.json
  • projectgraph/testdata/javascript/commonjs-require/indexes.json
  • projectgraph/testdata/javascript/default-import/graph.golden.json
  • projectgraph/testdata/javascript/default-import/indexes.json
  • projectgraph/testdata/javascript/esm-named-import/graph.golden.json
  • projectgraph/testdata/javascript/esm-named-import/indexes.json
  • projectgraph/testdata/javascript/export-star/graph.golden.json
  • projectgraph/testdata/javascript/export-star/indexes.json
  • projectgraph/testdata/javascript/exports-conditions/graph.golden.json
  • projectgraph/testdata/javascript/exports-conditions/indexes.json
  • projectgraph/testdata/javascript/unknown-receiver/graph.golden.json
  • projectgraph/testdata/javascript/unknown-receiver/indexes.json
  • projectgraph/testdata/javascript/workspaces/graph.golden.json
  • projectgraph/testdata/javascript/workspaces/indexes.json
  • projectgraph/testdata/python/all-hides/graph.golden.json
  • projectgraph/testdata/python/all-hides/indexes.json
  • projectgraph/testdata/python/assignment/graph.golden.json
  • projectgraph/testdata/python/assignment/indexes.json
  • projectgraph/testdata/python/class-body/graph.golden.json
  • projectgraph/testdata/python/class-body/indexes.json
  • projectgraph/testdata/python/decorators/graph.golden.json
  • projectgraph/testdata/python/decorators/indexes.json
  • projectgraph/testdata/python/external/graph.golden.json
  • projectgraph/testdata/python/external/indexes.json
  • projectgraph/testdata/python/local-receiver/graph.golden.json
  • projectgraph/testdata/python/local-receiver/indexes.json
  • projectgraph/testdata/python/member-call/graph.golden.json
  • projectgraph/testdata/python/member-call/indexes.json
  • projectgraph/testdata/python/missing-file/graph.golden.json
  • projectgraph/testdata/python/missing-file/indexes.json
  • projectgraph/testdata/python/package-init/graph.golden.json
  • projectgraph/testdata/python/package-init/indexes.json
  • projectgraph/testdata/python/reexport-cycle/graph.golden.json
  • projectgraph/testdata/python/reexport-cycle/indexes.json
  • projectgraph/testdata/python/relative-imports/graph.golden.json
  • projectgraph/testdata/python/relative-imports/indexes.json
  • projectgraph/testdata/python/sibling-import/graph.golden.json
  • projectgraph/testdata/python/sibling-import/indexes.json
  • projectgraph/testdata/python/src-layout/graph.golden.json
  • projectgraph/testdata/python/src-layout/indexes.json
  • projectgraph/testdata/python/sys-path/graph.golden.json
  • projectgraph/testdata/python/sys-path/indexes.json
  • projectgraph/testdata/python/wildcard/graph.golden.json
  • projectgraph/testdata/python/wildcard/indexes.json
  • projectgraph/testdata/roots/extra-rules/graph.golden.json
  • projectgraph/testdata/roots/extra-rules/indexes.json
  • projectgraph/testdata/roots/library-surface/graph.golden.json
  • projectgraph/testdata/roots/library-surface/indexes.json
  • projectgraph/testdata/roots/main-guard/graph.golden.json
  • projectgraph/testdata/roots/main-guard/indexes.json
  • projectgraph/testdata/roots/package-json-bin/graph.golden.json
  • projectgraph/testdata/roots/package-json-bin/indexes.json
  • projectgraph/testdata/roots/package-json-main/graph.golden.json
  • projectgraph/testdata/roots/package-json-main/indexes.json
  • projectgraph/testdata/roots/procfile-dockerfile/graph.golden.json
  • projectgraph/testdata/roots/procfile-dockerfile/indexes.json
  • projectgraph/testdata/roots/pyproject-scripts/graph.golden.json
  • projectgraph/testdata/roots/pyproject-scripts/indexes.json
  • projectgraph/testdata/roots/setup-py/graph.golden.json
  • projectgraph/testdata/roots/setup-py/indexes.json
  • projectgraph/testdata/roots/test-file/graph.golden.json
  • projectgraph/testdata/roots/test-file/indexes.json
  • projectgraph/testdata/typescript/js-specifier/graph.golden.json
  • projectgraph/testdata/typescript/js-specifier/indexes.json
  • projectgraph/testdata/typescript/tsconfig-paths/graph.golden.json
  • projectgraph/testdata/typescript/tsconfig-paths/indexes.json
  • projectgraph/testdata/typescript/type-only/graph.golden.json
  • projectgraph/testdata/typescript/type-only/indexes.json
  • projectgraph/walk.go
  • scripts/fetch_method_corpus.sh
  • scripts/pypi_advisories.py
  • test/projectgraph/README.md
  • test/projectgraph/bench_test.go
  • test/projectgraph/cases/go/dependency-build-files/summaries/go_github.com_acme_kit_v1.0.0.json
  • test/projectgraph/cases/go/dependency-generated-code/summaries/go_github.com_acme_kit_v1.0.0.json
  • test/projectgraph/cases/go/dependency-implements-interface/summaries/go_github.com_acme_dep_v1.0.0.json
  • test/projectgraph/cases/go/dependency-internal-call/summaries/go_github.com_acme_kit_v1.0.0.json
  • test/projectgraph/cases/go/dependency-module/summaries/go_github.com_acme_yamlish_v1.2.0.json
  • test/projectgraph/cases/go/dependency-platform-files/summaries/go_github.com_acme_kit_v1.0.0.json
  • test/projectgraph/cases/go/dependency-platform-results/summaries/go_github.com_acme_kit_v1.0.0.json
  • test/projectgraph/cases/go/dependency-result-types/summaries/go_example.com_markup_v1.0.0.json
  • test/projectgraph/cases/go/dependency-result-types/summaries/go_std_go1.25.1.json
  • test/projectgraph/cases/javascript/dependency-candidates/summaries/npm_store-kit_1.0.0.json
  • test/projectgraph/cases/javascript/dependency-exports-import/summaries/npm_kit_1.0.0.json
  • test/projectgraph/cases/javascript/dependency-exports-require/summaries/npm_kit_1.0.0.json
  • test/projectgraph/cases/javascript/dependency-method-name/summaries/npm_plug_1.0.0.json
  • test/projectgraph/cases/javascript/dependency-same-path/summaries/npm_util-kit_1.0.0.json
  • test/projectgraph/cases/javascript/dependency-transitive/summaries/npm_lodash._reinterpolate_3.0.0.json
  • test/projectgraph/cases/javascript/dependency-transitive/summaries/npm_lodash_4.17.21.json
  • test/projectgraph/cases/python/bundled-javascript/expect.yaml
  • test/projectgraph/cases/python/bundled-javascript/project/main.py
  • test/projectgraph/cases/python/call-result-local/expect.yaml
  • test/projectgraph/cases/python/call-result-local/project/factory.py
  • test/projectgraph/cases/python/call-result-local/project/main.py
  • test/projectgraph/cases/python/decorator-computed/expect.yaml
  • test/projectgraph/cases/python/decorator-computed/project/main.py
  • test/projectgraph/cases/python/decorator-factory/expect.yaml
  • test/projectgraph/cases/python/decorator-factory/project/main.py
  • test/projectgraph/cases/python/dependency-compiled-module/expect.yaml
  • test/projectgraph/cases/python/dependency-compiled-module/project/main.py
  • test/projectgraph/cases/python/dependency-compiled-module/project/venv/site-packages/speedy-1.0.0.dist-info/METADATA
  • test/projectgraph/cases/python/dependency-compiled-module/project/venv/site-packages/speedy-1.0.0.dist-info/RECORD
  • test/projectgraph/cases/python/dependency-compiled-module/project/venv/site-packages/speedy/__init__.py
  • test/projectgraph/cases/python/dependency-compiled-module/project/venv/site-packages/speedy/pure.py
  • test/projectgraph/cases/python/dependency-compiled-module/summaries/pypi_speedy_1.0.0.json
  • test/projectgraph/cases/python/dependency-dark-unit/summaries/pypi_PyYAML_6.0.1.json
  • test/projectgraph/cases/python/dependency-failed-reached/summaries/pypi_badpkg_1.0.0.json
  • test/projectgraph/cases/python/dependency-failed-reached/summaries/pypi_okpkg_1.0.0.json
  • test/projectgraph/cases/python/dependency-failed-unreached/summaries/pypi_badpkg_1.0.0.json
  • test/projectgraph/cases/python/dependency-failed-unreached/summaries/pypi_okpkg_1.0.0.json
  • test/projectgraph/cases/python/dependency-namespace-two-dists/expect.yaml
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/Procfile
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/main.py
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google/auth/__init__.py
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google/auth/crypt.py
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google/cloud/storage/__init__.py
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google_auth-2.0.0.dist-info/METADATA
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google_auth-2.0.0.dist-info/RECORD
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google_cloud_storage-2.1.0.dist-info/METADATA
  • test/projectgraph/cases/python/dependency-namespace-two-dists/project/venv/site-packages/google_cloud_storage-2.1.0.dist-info/RECORD
  • test/projectgraph/cases/python/dependency-namespace-two-dists/summaries/pypi_google-auth_2.0.0.json
  • test/projectgraph/cases/python/dependency-namespace-two-dists/summaries/pypi_google-cloud-storage_2.1.0.json
  • test/projectgraph/cases/python/dependency-package-dir/summaries/pypi_outer_1.0.0.json
  • test/projectgraph/cases/python/dependency-record-import-name/expect.yaml
  • test/projectgraph/cases/python/dependency-record-import-name/project/main.py
  • test/projectgraph/cases/python/dependency-record-import-name/project/venv/site-packages/PyYAML-6.0.1.dist-info/METADATA
  • test/projectgraph/cases/python/dependency-record-import-name/project/venv/site-packages/PyYAML-6.0.1.dist-info/RECORD
  • test/projectgraph/cases/python/dependency-record-import-name/project/venv/site-packages/yaml/__init__.py
  • test/projectgraph/cases/python/dependency-record-import-name/summaries/pypi_PyYAML_6.0.1.json
  • test/projectgraph/cases/python/dependency-site-packages/expect.yaml
  • test/projectgraph/cases/python/dependency-site-packages/summaries/pypi_requests_2.31.0.json
  • test/projectgraph/cases/python/dependency-site-packages/summaries/pypi_urllib3_2.0.7.json
  • test/projectgraph/cases/python/dependency-stdlib/expect.yaml
  • test/projectgraph/cases/python/dependency-stdlib/project/python/Lib/http/__init__.py
  • test/projectgraph/cases/python/dependency-stdlib/project/python/Lib/subprocess.py
  • test/projectgraph/cases/python/dependency-stdlib/project/python/Lib/test/test_threading.py
  • test/projectgraph/cases/python/dependency-stdlib/project/python/Lib/threading.py
  • test/projectgraph/cases/python/dependency-stdlib/project/server.py
  • test/projectgraph/cases/python/dependency-stdlib/summaries/python_std_3.12.3.json
  • test/projectgraph/cases/python/dependency-typed-return/expect.yaml
  • test/projectgraph/cases/python/dependency-typed-return/project/main.py
  • test/projectgraph/cases/python/dependency-typed-return/project/venv/site-packages/dep/__init__.py
  • test/projectgraph/cases/python/dependency-typed-return/summaries/pypi_dep_1.0.0.json
  • test/projectgraph/cases/python/external-base-attribute/expect.yaml
  • test/projectgraph/cases/python/flask-route/expect.yaml
  • test/projectgraph/cases/python/stdlib-name-deep/expect.yaml
  • test/projectgraph/cases/python/stdlib-name-deep/project/app/__init__.py
  • test/projectgraph/cases/python/stdlib-name-deep/project/app/models/__init__.py
  • test/projectgraph/cases/python/stdlib-name-deep/project/app/models/types.py
  • test/projectgraph/cases/python/stdlib-name-deep/project/main.py
  • test/projectgraph/cases/python/truncated-test-file/expect.yaml
  • test/projectgraph/cases/python/truncated-test-file/project/main.py
  • test/projectgraph/cases/python/truncated-test-file/project/tests/test_big.py
  • test/projectgraph/cases/python/typed-base-subclass/expect.yaml
  • test/projectgraph/cases/python/typed-base-subclass/project/main.py
  • test/projectgraph/cases/python/typed-base-subclass/project/venv/site-packages/dep/__init__.py
  • test/projectgraph/cases/python/typed-base-subclass/summaries/pypi_dep_1.0.0.json
  • test/projectgraph/cases/python/typed-field/expect.yaml
  • test/projectgraph/cases/python/typed-field/project/factory.py
  • test/projectgraph/cases/python/typed-field/project/main.py
  • test/projectgraph/cases/python/typed-parameter/expect.yaml
  • test/projectgraph/cases/python/typed-parameter/project/factory.py
  • test/projectgraph/cases/python/typed-parameter/project/main.py
  • test/projectgraph/cases/python/typed-return/expect.yaml
  • test/projectgraph/cases/python/typed-return/project/Procfile
  • test/projectgraph/cases/python/typed-return/project/factory.py
  • test/projectgraph/cases/python/typed-return/project/main.py
  • test/projectgraph/cases/rust/dependency-crate/summaries/cargo_kit_1.0.0.json
  • test/projectgraph/cases/rust/dependency-internal-call/summaries/cargo_kit_1.0.0.json
  • test/projectgraph/cases/rust/dependency-renamed/summaries/cargo_kit_1.0.0.json
  • test/projectgraph/cases/rust/library-build-script/summaries/cargo_kit_1.0.0.json
  • test/projectgraph/cases_test.go
  • test/projectgraph/helpers_test.go
  • test/projectgraph/methods_test.go
  • test/projectgraph/public/methods/advisories.json
  • test/projectgraph/public/methods/corpus.txt
  • test/projectgraph/receivers_test.go
  • test/projectgraph/report.md
  • test/projectgraph/testdata/receivers/python/dep/dep/__init__.py
  • test/projectgraph/testdata/receivers/python/dep/pyproject.toml
  • test/projectgraph/version_test.go

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T07:58:05.506008Z 9494392 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9494392f0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugin/callgraph/python.go
@codecov-commenter

codecov-commenter commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.81262% with 253 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (c270ee9) to head (618ad61).

Files with missing lines Patch % Lines
plugin/indexer/python.go 80.66% 19 Missing and 16 partials ⚠️
distgraph/graph.go 89.35% 18 Missing and 10 partials ⚠️
examples/distgraph/main.go 46.66% 19 Missing and 5 partials ⚠️
examples/projectgraph/main.go 0.00% 19 Missing ⚠️
projectgraph/modules/pydist.go 87.05% 9 Missing and 9 partials ⚠️
projectgraph/packages.go 46.66% 12 Missing and 4 partials ⚠️
projectgraph/declared.go 31.81% 14 Missing and 1 partial ⚠️
projectgraph/resolve.go 11.76% 14 Missing and 1 partial ⚠️
projectgraph/modules/python.go 17.64% 14 Missing ⚠️
projectgraph/python.go 22.22% 11 Missing and 3 partials ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #55      +/-   ##
==========================================
- Coverage   77.23%   77.13%   -0.10%     
==========================================
  Files         174      180       +6     
  Lines       17129    18135    +1006     
==========================================
+ Hits        13230    13989     +759     
- Misses       3115     3301     +186     
- Partials      784      845      +61     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…elper

The record of a call of the value of a call went away when the inner
callee was no reflection. operator.methodcaller("system", cmd)(os) calls
a method by a name in a string, as getattr(os, name)() does, and no edge
names that method. So os.system read as no-such-target. methodcaller and
attrgetter of the operator module count as reflective callees now, and
the outer call keeps its computed-name record.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
The link step did not know the class of a Python receiver that a type
hint names. A call on a typed parameter, on a local that holds the result
of a function with a return hint, or on a field of a class had a record
of an unknown receiver and low name edges. The receiver rows of Python
marked 16 forms as gaps.

The index step records the hints: the class of a parameter, with
Optional, Union and `| None` unwrapped, the return type and the tuple
positions of a function, an annotated local, a name that holds a call
result, a typed name of the top level of a module, and the fields of a
class from its annotations and from the attributes that its methods set.
A call on a typed name gets the key of the name in its scope as its
target, as the Go index does, so the link step reads the type. A call
that the call graph bound to a method of a class of the file keeps that
target. A result of a reflective call, as import_module, has no type.

The link step trusts the hints as it trusts TypeScript types. A hint of a
dependency class gives the external key of the method, the class
hierarchy analysis adds the overrides in the project and in the unit, and
an abstract method with no override is a record with the reason
interface-receiver. The walk now sets the class of an instance of an
external class, so the analysis runs for it.

All 16 Python receiver rows give their wanted answer. Six cases cover the
forms. The external-base-attribute case gets a member edge in place of a
name edge. The method measure adds a detail file per target.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
The method report had no section for step 3. This adds the numbers of
the run after the hints, the two targets that move, and why the rest do
not: the paths of the corpus cross dependency code with no hints.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
A caller of the project graph had no way to tell which installed Python
distribution imports which, as modgraph tells it for the modules of a Go
build. Without the edges, a caller cannot trust that no code imports a
distribution, and a vulnerable distribution hides behind a false unused.

distgraph builds the graph from the code on the disk. It reads the
.dist-info directories of each site-packages directory and the import
statements of the Python files of the project and of each distribution
that an import reaches. It runs no Python. The graph never misses an edge
that a run has: a distribution with an extension module, a computed
import, an import that nothing holds, a file that does not parse, or a
file past the budget is open, and a path that meets it reaches each
distribution. An import in a try statement that catches an ImportError
opens nothing. A distribution with no Python file gets its Requires-Dist
lines as evidence of edges.

The distribution reader gains the Requires-Dist names and the test-path
rule, which the index step shares. examples/distgraph prints the graph,
and docs/distgraph.md holds the rules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
A call of the standard library on self, as self.run in Thread.start,
reached the override of the project only through the reference edge of
the override rule, because the graph had no code of the library. A path
into a module of the library ended at its external key.

The Lib directory of CPython is now the unit python:std@<version>, as
GOROOT/src is go:std. The index step leaves out its test suite, the IDLE,
the turtle demos and lib2to3. The link step knows that the python
ecosystem installs Python, so a truncated file of the unit counts only
for a Python program. A class of a unit gives its external key to the
class hierarchy analysis, because a class of the project names that key
as its base, so Thread.run reaches Worker.run with a cha edge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
Two distributions that share a namespace package, as google-auth and
google-cloud-storage under google, had no case. The case shows that an
import of a module under the namespace resolves into the one unit that
holds it, and that no record names a unit that no import loads.

An ambiguous site keeps no method name, so it concerns every target. The
docs now say why: the site gets no edge, so the search cannot follow what
the candidates call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
The report had no section for steps 5 and 6. This adds a run of the
distribution graph on httpie of the corpus and what the standard library
case shows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149TuYSppnJAa4JVsXXqHDA
@abhisek abhisek changed the title feat(projectgraph): measure the Python reach and link pypi units by their .dist-info feat(projectgraph): link Python dependency code, read its type hints, and add the distribution graph Oct 24, 2026
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