| py-code-visualizer | pyan | code2flow | pydeps | pycallgraph2 | |
|---|---|---|---|---|---|
| Analysis type | Static (pure AST) | Static (AST) | Static (AST) | Static (imports) | Runtime tracing |
| Executes your code | Never | No | No | No | Yes (traces a run) |
| Graph level | Function-level calls | Function-level calls + uses | Function-level calls | Module imports | Function calls (of one run) |
| Languages | Python | Python | Python, JS, Ruby, PHP | Python | Python |
| Accuracy stance | Unresolvable calls flagged ambiguous with candidates — never guessed | — | Self-described “pretty good estimate” | — (imports are exact) | Exact for the traced run only |
| file:line provenance on every edge | Yes | — | — | — | — |
| Deterministic (byte-identical) output guarantee | Yes (tested) | — | — | — | — (run-dependent) |
| Outputs | Interactive offline HTML · Mermaid · JSON · C4 · SVG/PNG | GraphViz dot · yEd · HTML | DOT / images | SVG (via GraphViz) | Images (via GraphViz) |
| CI gates (cycles, layer rules) & PR diff reports | Yes (`check`, `diff`, `review`) | — | — | — | — |
| AI/agent export (verified context packs, AGENTS.md) | Yes (`context`, `export --for-ai`) | — | — | — | — |
“—” = not a documented feature per that tool’s own README/docs as of July 2026 (links in the header row). Corrections welcome — open an issue.
When the other tool is the better choice
- pydeps — you want a fast, mature module import overview and nothing more. Import graphs are exact by nature; if that's the whole question, pydeps answers it well.
- pycallgraph2 — you need to know what a specific execution actually did (including timing), and you're able to run the code. Runtime tracing answers a question static analysis can't.
- code2flow — your repo mixes Python with JavaScript, Ruby, or PHP and you want one polyglot estimate across all of it.
- pyan — you want a minimal dot/yEd emitter to feed an existing GraphViz pipeline.
Where py-code-visualizer is different by design
The dynamic-language call-graph problem is real — code2flow's README says it plainly:
“No algorithm can generate a perfect call graph for a dynamic language.” We agree,
and take the opposite engineering stance on what to do about it: emit only what is
provable, and flag the rest. Unresolvable calls become ambiguous
edges with their full candidate list — never a guess. Add byte-identical determinism and
file:line provenance, and the graph stops being a picture and becomes
infrastructure: something you can gate CI on, diff in PRs, and hand to an AI agent as
ground truth. See the measured facts.