Pyan
pyan is a Python module that performs static analysis of Python code to determine a call dependency graph between functions and methods. This is different from running the code and seeing which functions are called and how often; there are various tools that will generate a call graph in that way, usually using debugger or profiling trace hooks - for example: https://pycallgraph.readthedocs.org/ This code was originally written by Edmund Horner, and then modified by Juha Jeronen. See README for the original blog posts and links to their repositories.
Install / Use
/learn @davidfraser/PyanREADME
Pyan archive repository
This is an old repository of the Pyan source code, preserved for historical purposes - the actual development is done at Technologicat/pyan
There you can find versions from 2.0.0 onwards, and they are installable from PyPI (pip install pyan3).
Many of the TODO issues described below have been addressed, and as of writing this Pythons 3.10 through 3.14 are supported.
Pyan3 (original description)
Offline call graph generator for Python 3
Pyan takes one or more Python source files, performs a (rather superficial) static analysis, and constructs a directed graph of the objects in the combined source, and how they define or use each other. The graph can be output for rendering by GraphViz or yEd.
This project had 2 official repositories:
- The original stable davidfraser/pyan.
- Technologicat's repository Technologicat/pyan
Technologicat's repository is now the main repository where development is taking place, and this one is archived
The PyPI package pyan3 is built from Technologicat's repository
About
Defines relations are drawn with dotted gray arrows.
Uses relations are drawn with black solid arrows. Recursion is indicated by an arrow from a node to itself. Mutual recursion between nodes X and Y is indicated by a pair of arrows, one pointing from X to Y, and the other from Y to X.
Nodes are always filled, and made translucent to clearly show any arrows passing underneath them. This is especially useful for large graphs with GraphViz's fdp filter. If colored output is not enabled, the fill is white.
In node coloring, the HSL color model is used. The hue is determined by the filename the node comes from. The lightness is determined by depth of namespace nesting, with darker meaning more deeply nested. Saturation is constant. The spacing between different hues depends on the number of files analyzed; better results are obtained for fewer files.
Groups are filled with translucent gray to avoid clashes with any node color.
The nodes can be annotated by filename and source line number information.
Note
The static analysis approach Pyan takes is different from running the code and seeing which functions are called and how often. There are various tools that will generate a call graph that way, usually using a debugger or profiling trace hooks, such as Python Call Graph.
In Pyan3, the analyzer was ported from compiler (good riddance) to a combination of ast and symtable, and slightly extended.
Install
pip install pyan3
Usage
See pyan3 --help.
Example:
pyan *.py --uses --no-defines --colored --grouped --annotated --dot >myuses.dot
Then render using your favorite GraphViz filter, mainly dot or fdp:
dot -Tsvg myuses.dot >myuses.svg
Or use directly
pyan *.py --uses --no-defines --colored --grouped --annotated --svg >myuses.svg
You can also export as an interactive HTML
pyan *.py --uses --no-defines --colored --grouped --annotated --html > myuses.html
Alternatively, you can call pyan from a script
import pyan
from IPython.display import HTML
HTML(pyan.create_callgraph(filenames="**/*.py", format="html"))
Sphinx integration
You can integrate callgraphs into Sphinx.
Install graphviz (e.g. via sudo apt-get install graphviz) and modify source/conf.py so that
# modify extensions
extensions = [
...
"sphinx.ext.graphviz"
"pyan.sphinx",
]
# add graphviz options
graphviz_output_format = "svg"
Now, there is a callgraph directive which has all the options of the graphviz directive and in addition:
- :no-groups: (boolean flag): do not group
- :no-defines: (boolean flag): if to not draw edges that show which functions, methods and classes are defined by a class or module
- :no-uses: (boolean flag): if to not draw edges that show how a function uses other functions
- :no-colors: (boolean flag): if to not color in callgraph (default is coloring)
- :nested-grops: (boolean flag): if to group by modules and submodules
- :annotated: (boolean flag): annotate callgraph with file names
- :direction: (string): "horizontal" or "vertical" callgraph
- :toctree: (string): path to toctree (as used with autosummary) to link elements of callgraph to documentation (makes all nodes clickable)
- :zoomable: (boolean flag): enables users to zoom and pan callgraph
Example to create a callgraph for the function pyan.create_callgraph that is
zoomable, is defined from left to right and links each node to the API documentation that
was created at the toctree path api.
.. callgraph:: pyan.create_callgraph
:toctree: api
:zoomable:
:direction: horizontal
Troubleshooting
If GraphViz says trouble in init_rank, try adding -Gnewrank=true, as in:
dot -Gnewrank=true -Tsvg myuses.dot >myuses.svg
Usually either old or new rank (but often not both) works; this is a long-standing GraphViz issue with complex graphs.
Too much detail?
If the graph is visually unreadable due to too much detail, consider visualizing only a subset of the files in your project. Any references to files outside the analyzed set will be considered as undefined, and will not be drawn.
Currently Pyan always operates at the level of individual functions and methods; an option to visualize only relations between namespaces may (or may not) be added in a future version.
Features
Items tagged with ☆ are new in Pyan3.
Graph creation:
- Nodes for functions and classes
- Edges for defines
- Edges for uses
- This includes recursive calls ☆
- Grouping to represent defines, with or without nesting
- Coloring of nodes by filename
- Unlimited number of hues ☆
Analysis:
- Name lookup across the given set of files
- Nested function definitions
- Nested class definitions ☆
- Nested attribute accesses like
self.a.b☆ - Inherited attributes ☆
- Pyan3 looks up also in base classes when resolving attributes. In the old Pyan, calls to inherited methods used to be picked up by
contract_nonexistents()followed byexpand_unknowns(), but that often generated spurious uses edges (because the wildcard to*.nameexpands toX.namefor allXthat have an attribute calledname.).
- Pyan3 looks up also in base classes when resolving attributes. In the old Pyan, calls to inherited methods used to be picked up by
- Resolution of
super()based on the static type at the call site ☆ - MRO is (statically) respected in looking up inherited attributes and
super()☆ - Assignment tracking with lexical scoping
- E.g. if
self.a = MyFancyClass(), the analyzer knows that any references toself.apoint toMyFancyClass - All binding forms are supported (assign, augassign, for, comprehensions, generator expressions, with) ☆
- Name clashes between
forloop counter variables and functions or classes defined elsewhere no longer confuse Pyan.
- Name clashes between
- E.g. if
selfis defined by capturing the name of the first argument of a method definition, like Python does. ☆- Simple item-by-item tuple assignments like
x,y,z = a,b,c☆ - Chained assignments
a = b = c☆ - Local scope for lambda, listcomp, setcomp, dictcomp, genexpr ☆
- Keep in mind that list comprehensions gained a local scope (being treated like a function) only in Python 3. Thus, Pyan3, when applied to legacy Python 2 code, will give subtly wrong results if the code uses list comprehensions.
- Source filename and line number annotation ☆
- The annotation is appended to the node label. If grouping is off, namespace is included in the annotation. If grouping is on, only source filename and line number information is included, because the group title already shows the namespace.
TODO
- Determine confidence of detected edges (probability that the edge is correct). Start with a binary system, with only values 1.0 and 0.0.
- A fully resolved reference to a name, based on lexical scoping, has confidence 1.0.
- A reference to an unknown name has confidence 0.0.
- Attributes:
- A fully resolved reference to a known attribute of a known object has confidence 1.0.
- A reference to an unknown attribute of a known object has confidence 1.0. These are mainly generated by imports, when the imported file is not in the analyzed set. (Does this need a third value, such as 0.5?)
- A reference to an attribute of an unknown object has confidence 0.0.
- A wildcard and its expansions have confidence 0.0.
- Effects of binding analysis? The system should not claim full confidence in a bound value, unless it fully understands both the binding syntax and the value. (Note that this is very restrictive. A function call or a list in the expression for the value will currently spoil the full analysis.)
- Confidence values may need updating in pass 2.
- Make the analyzer understand
del name(probably seen asisinstance(node.ctx, ast.Del)invisit_Name(),visit_Attribute()) - Prefix methods by class name in the graph; create a legend for annotations. See the discussion here.
- Improve the wildcard resolution mechanism, see discussion here.
- Could record the namespace of the use site upon creating the wildcard, and check any possible resolutions against that (requiring that the resolved name is in scope at the use site)?
- Add an option to visualize relations only between namespaces, useful for large projects.
- Scan the nodes and edges, basically generate a new graph and visualize that.
- Publish test ca
Related Skills
node-connect
338.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.4kCommit, push, and open a PR

