Giter Club home page Giter Club logo

pyan's Introduction

Pyan3

Offline call graph generator for Python 3

Build Status FOSSA Status Codacy Badge PyPI - Python Version

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 has 2 official repositories:

The PyPI package pyan3 is built from development

Help wanted! [November 2023]

The last major analyzer upgrades to pyan were made several years ago, for Python 3.6. If pyan has worked at all for Python 3.7+, that is pure luck.

It pains me to say, but as I am sure you all have noticed by the inactivity, I do not have the resources to keep pyan alive, nor to fix its many design issues.

Therefore:

Since there is a continuing community interest in pyan, I would like to hand over the pyan project to the community, including write access to the PyPI package.

If interested, you can contact me by posting a comment on this issue.

Background

I'm in a position that is rather peculiar for a software developer, where I have not needed a static analyzer in many years. Although as a computational scientist, I write my own research codes in Python, in practice:

  • 90% of the time, I am writing new code to solve some new problem, or writing sufficiently different code that existing implementations are not applicable. For some concrete examples:
  • The other 10% of the time, I am looking at either my own old code, or at something modular enough (e.g. how some particular feature of TensorFlow works internally), so that a local manual analysis is sufficient.
  • As a subfield of software engineering, numerics is an odd one out. Typical features are:
    • Control flow is almost fully linear. Most solvers are scripts with hardcoded parameters (which are, at most, imported from a config.py -- which is as good as a configuration plaintext file as any).
    • The dependency tree is very simple, and the degree of modularization is very high.
    • The functional programming paradigm (or REST paradigm) is readily applicable: data in, data out, no internal state.
    • Algorithms are the highly nontrivial part.

Therefore, in my work, a structural analyzer such as pyan does not help much. As we all know, this is not the case in most other kinds of software engineering.

Of my Python metaprogramming projects, I am semi-actively using some parts of unpythonic. I also have some interest in keeping the mcpyrate macro expander alive, because it goes much further than earlier designs, and no one else seems to have run off with the mantle yet. But that is basically all I can afford in the foreseeable future.

Even for those two projects, as of this writing, the most recent opportunity I had to work on them was almost two years ago. So even the projects I am maintaining, currently only support up to Python 3.10.

--Technologicat

About

Example output

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 by expand_unknowns(), but that often generated spurious uses edges (because the wildcard to *.name expands to X.name for all X that have an attribute called name.).
  • 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 to self.a point to MyFancyClass
    • All binding forms are supported (assign, augassign, for, comprehensions, generator expressions, with) ☆
      • Name clashes between for loop counter variables and functions or classes defined elsewhere no longer confuse Pyan.
  • self is 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 as isinstance(node.ctx, ast.Del) in visit_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 cases.
  • Get rid of self.last_value?
    • Consider each specific kind of expression or statement being handled; get the relevant info directly (or by a more controlled kind of recursion) instead of self.visit().
    • At some point, may need a second visitor class that is just a catch-all that extracts names, which is then applied to only relevant branches of the AST.
    • On the other hand, maybe self.last_value is the simplest implementation that extracts a value from an expression, and it only needs to be used in a controlled manner (as analyze_binding() currently does); i.e. reset before visiting, and reset immediately when done.

The analyzer does not currently support:

  • Tuples/lists as first-class values (currently ignores any assignment of a tuple/list to a single name).
    • Support empty lists, too (for resolving method calls to .append() and similar).
  • Starred assignment a,*b,c = d,e,f,g,h
  • Slicing and indexing in assignment (ast.Subscript)
  • Additional unpacking generalizations (PEP 448, Python 3.5+).
    • Any uses on the RHS at the binding site in all of the above are already detected by the name and attribute analyzers, but the binding information from assignments of these forms will not be recorded (at least not correctly).
  • Enums; need to mark the use of any of their attributes as use of the Enum. Need to detect Enum in bases during analysis of ClassDef; then tag the class as an enum and handle differently.
  • Resolving results of function calls, except for a very limited special case for super().
    • Any binding of a name to a result of a function (or method) call - provided that the binding itself is understood by Pyan - will instead show in the output as binding the name to that function (or method). (This may generate some unintuitive uses edges in the graph.)
  • Distinguishing between different Lambdas in the same namespace (to report uses of a particular lambda that has been stored in self.something).
  • Type hints (PEP 484, Python 3.5+).
  • Type inference for function arguments
    • Either of these two could be used to bind function argument names to the appropriate object types, avoiding the need for wildcard references (especially for attribute accesses on objects passed in as function arguments).
    • Type inference could run as pass 3, using additional information from the state of the graph after pass 2 to connect call sites to function definitions. Alternatively, no additional pass; store the AST nodes in the earlier pass. Type inference would allow resolving some wildcards by finding the method of the actual object instance passed in.
    • Must understand, at the call site, whether the first positional argument in the function def is handled implicitly or not. This is found by looking at the flavor of the Node representing the call target.
  • Async definitions are detected, but passed through to the corresponding non-async analyzers; could be annotated.
  • Cython; could strip or comment out Cython-specific code as a preprocess step, then treat as Python (will need to be careful to get line numbers right).

How it works

From the viewpoint of graphing the defines and uses relations, the interesting parts of the AST are bindings (defining new names, or assigning new values to existing names), and any name that appears in an ast.Load context (i.e. a use). The latter includes function calls; the function's name then appears in a load context inside the ast.Call node that represents the call site.

Bindings are tracked, with lexical scoping, to determine which type of object, or which function, each name points to at any given point in the source code being analyzed. This allows tracking things like:

def some_func():
    pass

class MyClass:
    def __init__(self):
        self.f = some_func

    def dostuff(self)
        self.f()

By tracking the name self.f, the analyzer will see that MyClass.dostuff() uses some_func().

The analyzer also needs to keep track of what type of object self currently points to. In a method definition, the literal name representing self is captured from the argument list, as Python does; then in the lexical scope of that method, that name points to the current class (since Pyan cares only about object types, not instances).

Of course, this simple approach cannot correctly track cases where the current binding of self.f depends on the order in which the methods of the class are executed. To keep things simple, Pyan decides to ignore this complication, just reads through the code in a linear fashion (twice so that any forward-references are picked up), and uses the most recent binding that is currently in scope.

When a binding statement is encountered, the current namespace determines in which scope to store the new value for the name. Similarly, when encountering a use, the current namespace determines which object type or function to tag as the user.

Authors

See AUTHORS.md.

License

GPL v2, as per comments here.

pyan's People

Contributors

davidfraser avatar ejrh avatar jdb78 avatar johnyf avatar rakanalanazi avatar scrdest avatar sirex avatar technologicat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pyan's Issues

__init__() got multiple values for argument 'root'

Issue
Calling pyan3 from command line exits with a TypeError

Traceback (most recent call last):
  File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/local/lib/python3.7/dist-packages/pyan/main.py", line 244, in <module>
    main()
  File "/usr/local/lib/python3.7/dist-packages/pyan/main.py", line 206, in main
    v = CallGraphVisitor(filenames, logger, root=root)
TypeError: __init__() got multiple values for argument 'root'

To reproduce
Install using pip and git master: pip3 install git+https://github.com/Technologicat/pyan.git
Run pyan3 with parameters: pyan3 application.py --dot > test.dot

Environment
Python 3.7.10 VIrtualEnv on Ubuntu 21.04

Use bidirectional edges where possible

If A -> B and B -> A (where A, B are nodes), use graphviz's concentrate=true option to create a bidirectional edge instead of two unidirectional edges. See example.

This could reduce clutter in the resulting visualization, especially for modvis, where bidirectional implicit dependencies on a package's __init__ module are common.

enh: ordered sequence of functions

First of all, wanted to thank you for this excelent project. I've been looking for something like this for a long time. It's impressive, thanks!.

Now, I've used it with a couple of codes I have, and I really like the results. However, I was thinking if you may have this in the TODO list:

  1. ability to show an ordered sequence of functions. For example, the render produced by the tool nicely shows how the functions are related among them even showing the direction of the connection, but its not clear from the graph which function is called first, which second, etc. Example, this is an actual output of a code of mine:
    image
    In here you can tell the relationship between the pink function and the green ones, but you cannot tell which of the green ones is called first, second, etc.
    Indeed, in this example that I'm showing, the best and correct representation would be like this: pink_out -> green1 -> green2 -> ... -> greenN -> pink_in.

  2. There are situations where one function or another one will be triggered, depending on the output of an if-else-statement. Is it possible to obtain something like this?

Kinds regards!

Lucas

pyan3 is crashing all the time

Every time i try pyan3 on different projects it crash with the following results

$ pyan3 sun.py
Traceback (most recent call last):
  File "c:\users\anders\anaconda3\envs\py36\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\users\anders\anaconda3\envs\py36\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\anders\anaconda3\envs\py36\Scripts\pyan3.exe\__main__.py", line 7, in <module>
  File "c:\users\anders\anaconda3\envs\py36\lib\site-packages\pyan\main.py", line 206, in main
    v = CallGraphVisitor(filenames, logger, root=root)
TypeError: __init__() got multiple values for argument 'root'

Any ideas how to fix this?

I tried it on both Python 3.6 and 3.8 with the same results.

Crash on lambda parameter

Running pyan 1.1.1 on following trivial example
def func(x=lambda a,b:a): return x(1,2)

Gives following error

File "/usr/local/lib/python3.9/site-packages/pyan/analyzer.py", line 468, in visit_Lambda
with ExecuteInInnerScope(self, "lambda"):
File "/usr/local/lib/python3.9/site-packages/pyan/anutils.py", line 224, in enter
raise ValueError("Unknown scope '%s'" % (inner_ns))
ValueError: Unknown scope 'test2.func.lambda'

Fix hashbang in pyan3 script

Currently hardcoded to #!/usr/bin/python3. Should be #!/usr/bin/env python3 to ask the OS for the currently active Python.

pyan3 cannot run

I tried to make an html output using:
pyan *.py --uses --no-defines --colored --grouped --annotated --html > myuses.html

returned:
Usage: pyan FILENAME... [--dot|--tgf|--yed]
pyan: error: no such option: --html

When run command: pyan3 -help
the result is:
Traceback (most recent call last):
File "/home/yuan/anaconda3/envs/pyan3/bin/pyan3", line 7, in
from pyan import main
ImportError: cannot import name 'main'

run pyan -h:

Usage: pyan FILENAME... [--dot|--tgf|--yed]

Analyse one or more Python source files and generate anapproximate call graph
of the modules, classes and functions within them.

Options:
-h, --help show this help message and exit
--dot output in GraphViz dot format
--tgf output in Trivial Graph Format
--yed output in yEd GraphML Format
-f FILE, --file=FILE write graph to FILE
-l LOG, --log=LOG write log to LOG
-v, --verbose verbose output
-V, --very-verbose even more verbose output (mainly for debug)
-d, --defines add edges for 'defines' relationships [default]
-n, --no-defines do not add edges for 'defines' relationships
-u, --uses add edges for 'uses' relationships [default]
-N, --no-uses do not add edges for 'uses' relationships
-c, --colored color nodes according to namespace [dot only]
-G, --grouped-alt suggest grouping by adding invisible defines edges
[only useful with --no-defines]
-g, --grouped group nodes (create subgraphs) according to namespace
[dot only]
-e, --nested-groups create nested groups (subgraphs) for nested namespaces
(implies -g) [dot only]
--dot-rankdir=RANKDIR
specifies the dot graph 'rankdir' property for
controlling the direction of the graph. Allowed
values: ['TB', 'LR', 'BT', 'RL']. [dot only]
-a, --annotated annotate with module and source line number
--make-svg try to run dot to make svg automatically

Add support for Python 3.8 and later

We need to support ast.NamedExpr a.k.a. the walrus operator (name := value).

That's probably the only AST change that Pyan needs to be aware of. To be sure, check GTS, and mcpyrate's unparser (to which I added Python 3.8 support already).

Relative imports crash pyan

Pyan does not currently support from . import foo, and crashes when it sees one.

For relative imports, the module attribute of the ImportFrom node is None.

We need to detect this case, and then also handle the level attribute, and actually figure out the correct absolute module path (at least as far as Pyan can reasonably detect) based on the relative one.

See GTS on imports.

pip install from pypi is broken for venvs

Here is what I got when installed in fresh temporary python venv:

➜  ~ mktmpenv --python=/usr/bin/python3
Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/ttylec/.virtualenvs/tmp-195183325368ab1e/bin/python3
Also creating executable in /home/ttylec/.virtualenvs/tmp-195183325368ab1e/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.
This is a temporary environment. It will be deleted when you run 'deactivate'.
(tmp-195183325368ab1e) ➜  tmp-195183325368ab1e python --version
Python 3.6.8
(tmp-195183325368ab1e) ➜  tmp-195183325368ab1e pip install pyan3
Collecting pyan3
  Using cached https://files.pythonhosted.org/packages/1a/82/d1b47fbcde2472d5954b4ed70cc3acf67d27bb0e83cf9b5cd5785684c629/pyan3-1.0.4-py3-none-any.whl
Installing collected packages: pyan3
Successfully installed pyan3-1.0.4
(tmp-195183325368ab1e) ➜  tmp-195183325368ab1e pyan3
Traceback (most recent call last):
  File "/home/ttylec/.virtualenvs/tmp-195183325368ab1e/bin/pyan3", line 7, in <module>
    from pyan import main
ModuleNotFoundError: No module named 'pyan'

Installing from within cloned repo works.

Asyncio support

I have encountered problems while using pyan3 with codes involving asyncio lib.

The error is:

File "F:\Anaconda3\lib\site-packages\pyan\analyzer.py", line 815, in analyze_functiondef
    raise TypeError("Expected ast.FunctionDef; got %s" % (type(ast_node)))
TypeError: Expected ast.FunctionDef; got <class '_ast.AsyncFunctionDef'>

And the simplest proposed solution would be probably changing lines 814 a 815 in analyzer.py to:

if not isinstance(ast_node, ast.FunctionDef) and not isinstance(ast_node, ast.AsyncFunctionDef):
            raise TypeError("Expected ast.(Async)FunctionDef; got %s" % (type(ast_node)))

(Worked for me.)

README: Clarify status of Pyan repos on GitHub

Due to historical reasons, there is constantly some confusion about the Pyan repositories.

To clear it up, in the next release, place something like this at the start of the README:


Official Pyan repositories (as of autumn 2019):

Pyan no longer supports Python 2.x. If you still need to analyze Python 2 code, the stable repo has an archive of the final version of Pyan2.

The PyPI package pyan3 is built from development.

Development is manually synced to stable (via a PR) every now and then.

To future maintainers: when you take over, please update the link to the active development repo, and the date.

Creating a list of all possible function paths

Pyan has been super helpful to me! However, I would like to use it in a different way to output, in addition to the current graphs, a list of all possible functions' paths start->end.
Right now, the Graph edges include the source and the target (e.g., A -> B and another edge B -> C). What I want to do is to print (A -> B -> C).
Is that currently supported? If not, where to start? Any guidance is appreciated.

#Assume we have the following functions:
def A(): 
    C()
    B()
def B():
    C()

I want the output to be something like:
A -> C -> D
A -> B -> C -> D

Requirements on python files? -- failing to graph

Hello, I'm having issues with pyan. Specifically, when I try to graph my python files, I get blank graph.
I tested pyan with a simple test.py file and it works fine, but my large program shows nothing.
Is there specific programming style it expects? I'm not sure if this is a style issue, or an issue with I have an associated library it calls ? or something else. The only thing I can think of is I have a weird mix of global variables and calls to classes in my library file.

When I put both the library and my main file on the command line calling pyan , it graphs only the library file. Does that seem right?

I can't post the code it is private.

I have python 3.8 on windows 64 bit.

Enh: postprocessing: coarse-graining, call path listing

Pyan3 could use some postprocessing support:

  • In a mid-sized or large project, it's currently easy to lose sight of the forest for all the trees (pun not intended). The visualization of all individual functions provides way too much detail to get a useful overview of the whole codebase. It would be useful to restrict the view to show just the uses-relations between classes, or even just modules, hiding the detail.

  • See #1 about listing all call paths. The comments include a very rough first cut of untested example code explaining how this could be done.

Calling pyan3 without parametters show error

Calling Pyan3 without parameters show error

Normally when we use a CLI without passing parameters the application show the help information:

Error log:

Traceback (most recent call last):
  File "/home/eduardo/git/pyan/venv/bin/pyan3", line 4, in <module>
    __import__('pkg_resources').run_script('pyan3==1.0.5', 'pyan3')
  File "/home/eduardo/git/pyan/venv/lib/python3.8/site-packages/pkg_resources/__init__.py", line 666, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/home/eduardo/git/pyan/venv/lib/python3.8/site-packages/pkg_resources/__init__.py", line 1451, in run_script
    raise ResolutionError(
pkg_resources.ResolutionError: Script 'scripts/pyan3' not found in metadata at '/home/eduardo/git/pyan/venv/lib/python3.8/site-packages/pyan3-1.0.5.egg-info'

Installation is failing

I am trying to install pyan on my Ubuntu 18.04 machine but I am getting the following error -

narora@FR-1603:~/Downloads/Python-3.8.2$ sudo pip install pyan3
Collecting pyan3
  Downloading https://files.pythonhosted.org/packages/1c/00/ddbea36dae59a16a7a3facbcf2a228e36360ce0cde323fd05a078dfb1d43/pyan3-1.0.4.tar.gz (43kB)
    100% |████████████████████████████████| 51kB 1.8MB/s 
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-LKOzlG/pyan3/setup.py", line 97
        print("WARNING: Version information not found in '%s', using placeholder '%s'" % (init_py_path, version), file=sys.stderr)
                                                                                                                      ^
    SyntaxError: invalid syntax
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-LKOzlG/pyan3/

Syntax error give by pyan

@Technologicat hi thanks for working on pyan , currently i face issues for higher version of python3.6 which has different syntax as shown below

print(f"Kitti info train file is saved to {filename}")
SyntaxError: invalid syntax

How to solve this ??

Type hints cause crash

I think something from the typing module needs to be imported... I get this error when trying to run pyan3:

pyan3 
Traceback (most recent call last):
  File "/home/pjfasano/.local/bin/pyan3", line 5, in <module>
    from pyan.main import main
  File "/home/pjfasano/.local/lib/python3.6/site-packages/pyan/__init__.py", line 26, in <module>
    grouped: bool = True,
NameError: name 'Union' is not defined

modvis: allow omitting __init__ from output

The implicitly imported __init__ module of packages causes clutter in the output.

Sometimes it's useful to see, but especially for larger projects, an option to omit it could be useful.

Can not build graph from most of the projects I have tried

Hello,

Thanks for maintaining pyan. I have been trying to use pyan to generate caller-callee relationship in trivial graph format (tgf) for my research work. For example, to get static call graph of this (https://github.com/tiangolo/fastapi) repo I have tried the following commands

/home/avijit/Github/updated_pyan/pyan/pyan3 *.py --uses --no-defines --colored --grouped --annotated -f fastapi.txt --uses --no-defines --tgf
By executing above command, I am getting empty file with a '#' which seems like glob failed to load any .py file.

/home/avijit/Github/updated_pyan/pyan/pyan3 fastapi/*.py --uses --no-defines --colored --grouped --annotated -f fastapi.txt --uses --no-defines --tgf

When I tried to point to the source files in fastapi folder, I am getting the following error.

Screen Shot 2020-09-13 at 6 06 03 PM

I want to execute pyan for different popular python repositories on Github with nested folder structures which pyan fails most of the time. Please let me know if there is any workaround to make pyan working to generate tgf file for python 3 projects. I would be glad to contribute if there is any need.

Thanks
Avijit

Pyan crashes when i give filename or don't give

pyan *.py --uses --no-defines --colored --grouped --annotated --dot >myuses.dot
Traceback (most recent call last):
  File "/home/darknight/.local/bin/pyan", line 143, in <module>
    main()
  File "/home/darknight/.local/bin/pyan", line 115, in main
    v = CallGraphVisitor(filenames, logger)
  File "/home/darknight/.local/lib/python3.8/site-packages/pyan/analyzer.py", line 60, in __init__
    mod_name = get_module_name(filename)
  File "/home/darknight/.local/lib/python3.8/site-packages/pyan/anutils.py", line 40, in get_module_name
    is_root = any([f == "__init__.py" for f in os.listdir(potential_root)])
FileNotFoundError: [Errno 2] No such file or directory: ''

Update PyPI package

The PyPI package pyan3 is seriously out of date. Update it. EDIT: It's also broken, hence the bug tag.

Wait for #31 to get merged first, so we get support for relative imports.

TODO: Read through all open issues, and link relevant ones below so we can track which ones will be closed when we update the package.

RuntimeError: dictionary changed size during iteration

Getting this on a specific directory (not seeing it elsewhere)
What do you need to dig into this?

Traceback (most recent call last):
File "/Users/nevep/.pyenv/bin/versions/3.7.4/envs/egs-api/bin/pyan3", line 7, in
exec(compile(f.read(), file, 'exec'))
File "/Users/nevep/bepress/pyan/pyan3", line 11, in
sys.exit(main())
File "/Users/nevep/bepress/pyan/pyan/main.py", line 109, in main
v = CallGraphVisitor(filenames, logger)
File "/Users/nevep/bepress/pyan/pyan/analyzer.py", line 77, in init
self.process()
File "/Users/nevep/bepress/pyan/pyan/analyzer.py", line 87, in process
self.postprocess()
File "/Users/nevep/bepress/pyan/pyan/analyzer.py", line 154, in postprocess
self.collapse_inner()
File "/Users/nevep/bepress/pyan/pyan/analyzer.py", line 1562, in collapse_inner
for name in self.nodes:
RuntimeError: dictionary changed size during iteration

Enh: static type annotations

Trying to analyze code that uses static type annotations (specifically PEP 526) crashes Pyan.

This is due to a bug in analyzer.py; an AnnAssign AST node has a format different from a regular Assign node, but CallGraphVisitor currently does not know this.

Issue reported by and minimal example courtesy of @AgenttiX.

pyan_err.zip

NameError: name 'Union' is not defined

hello, I tried to install directly(use setup.py), and everything is ok during the process, but when I run, I got this:

Traceback (most recent call last):
File "/home/xxx/.local/bin/pyan3", line 33, in
sys.exit(load_entry_point('pyan==1.0.5', 'console_scripts', 'pyan3')())
File "/home/xxx/.local/bin/pyan3", line 25, in importlib_load_entry_point
return next(matches).load()
File "/home/xxx/.local/lib/python3.6/site-packages/importlib_metadata/init.py", line 105, in load
module = import_module(match.group('module'))
File "/usr/lib/python3.6/importlib/init.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "", line 994, in _gcd_import
File "", line 971, in _find_and_load
File "", line 941, in _find_and_load_unlocked
File "", line 219, in _call_with_frames_removed
File "", line 994, in _gcd_import
File "", line 971, in _find_and_load
File "", line 955, in _find_and_load_unlocked
File "", line 656, in _load_unlocked
File "", line 626, in _load_backward_compatible
File "/home/xxx/.local/lib/python3.6/site-packages/pyan-1.0.5-py3.6.egg/pyan/init.py", line 26, in
NameError: name 'Union' is not defined

So how can I resolve this?

Set up pyan3 as an entrypoint

The recommended way to set up command-line scripts is nowadays using the entry_points option of setup.py.

The pyan3 wrapper script should be replaced by a setup.py entrypoint named pyan3, pointing to pyan.main:main, to ensure the wrapper will work everywhere command-line Python does.

See e.g. Python project maturity checklist by Michał Karzyński.

Depth parameter to create_callgraph

It would be a nice feature to have if a user can specify depth create_callgraph should go for searching python files in a directory if a user has a nested subdirectory structure.
Does anyone feel the same?

Automatic search of files to read inside source code, perhaps?

Just to keep track of this feature request. :)

It would be nice if pyan could look inside a source code with many imports and decide which files it should also analyze. Python module finder could be useful. Of course, we would need to think about standard libs, which may or may not be interesting to have in the call graph. Hope it helps!

Installing pyan3

Hello, and sorry if my errors are obvious, I'm relativly new.
So after installing pyan3 using
pip install pyan3
I don't find a way to put it into the PATH of windows 10 so I can use the projet with powershell like in the guidelines
pyan *.py --uses --no-defines --colored --grouped --annotated --dot >myuses.dot
I tried to put the directory into the PATH "C:\ProgramData\Anaconda3\Lib\site-packages\pyan" or "C:\ProgramData\Anaconda3\Lib\site-packages" and it didn't work

Thanks in advance for anyone who will read and put they minds into it, and sorry if it's a stupid error and my part, anyway, have a good day

Analyzing an __init__.py in cwd crashes Pyan

The scope naming internals of objects at the top level of a module seem to have changed somewhere between Python 3.4 and 3.6.

This code used to work in Python 3.4 (pyan/analyzer.py, method analyze_scopes):

ns = "%s.%s" % (parent_ns, sc.name) if len(sc.name) else parent_ns

but in Python 3.6.8, I'm now getting names like ".some_top_level_object" (note silly leading dot).

Now parent_ns is empty, and the object name is returned in sc.name. It used to be the other way around.

Seems this needs code to handle both cases...

Setting up shared PyPI maintainership

I think now would be a good time to add you (@johnyf and @jdb78) as Pyan maintainers on PyPI so you'll be able to package new releases, too.

For that, I'll need your PyPI usernames, so I'll know which users to invite. You can post the username here, or if you prefer to tell me privately, my email address can be found in pyan's setup.py.

Just in case if you haven't yet registered a PyPI account, you can easily do so on pypi.org.

Syntax error in debug line

This line:

self.filenaame, node.lineno))

erroneously references filenaame, an attribute that does not exist. Additionally, the line above it mentions values[0] when values cannot be subscripted in some cases. I found success commenting this whole statement out - in fact, my mips_to_c repository is a success story for your support of Python 3.6+ type hinting, so thank you for implementing that!

Installation fails: conda, pip, Python 2

ERROR: Complete output from command python setup.py egg_info:
ERROR: Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/tmp/pip-req-build-PPzXEv/setup.py", line 97
    print("WARNING: Version information not found in '%s', using placeholder '%s'" % (init_py_path, version), file=sys.stderr)
                                                                                                                  ^
SyntaxError: invalid syntax
----------------------------------------

ERROR: Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-req-build-PPzXEv/

Pip 19.1.1
Conda 4.6.14

Discussion - next steps?

Hi everyone assigned,

Now that we all have write access to this repo, Pyan is becoming a true community project. I thought this would be a good time to take a moment to discuss Pyan's next steps. The tool is still far from perfect, but judging by the stars on GitHub (both here and on @davidfraser's stable repo), there's community demand for this kind of static analysis tool, and it's undoubtedly already useful.

In my open-source metaprogramming efforts, I'm currently focusing on other projects, both of which have turned out as major time sinks, so I won't likely have that much time to devote to Pyan in the near future.

Nevertheless, here are some short to medium term goals off the top of my head:

  • #51. Python 3.8 support is important for keeping Pyan alive for the next few years. It should only require small changes. I recently did this for mcpyrate, so the AST changes are still in recent memory for me. I can do this one.
  • README needs updating. Maybe the TODO section should be refactored into its own file. I can do this one, too.
  • The function and namespace filters added by @jdb78 need documentation in the README. (Nice feature, by the way! Thanks!)
  • Integrating modvis as an option for the main pyan executable? It's sometimes useful to get a 30000ft (9144m) view of a codebase, looking only at module dependencies, especially when there are many small modules. So some time ago, I wrote this small, separate tool and archived it here, but it's not yet integrated with the rest of Pyan (indeed, it's not even installed with the package).
  • We need many more automated tests. This would also document what exactly is guaranteed to be supported, in terms of the target codebase being analyzed. But I don't have the time to write lots of test cases now. As a stopgap measure, maybe add tests when new bugs are reported?
  • Renaming from pyan3 to pyan now that Python 2 is gone. This would be ideal to resolve in the near future. We need to set up a new PyPI package, and a redirect for the old one that, if possible, says the package name pyan3 is deprecated and pulls in the new one. We should probably set up a pyan3 console script too, that gives a deprecation warning, and then redirects to the new pyan console script. (I haven't yet thought about how to do any of this; hence I just released 1.1.1 quickly and skipped over this.)
  • Let's start keeping an official CHANGELOG, and move to semantic versioning?
  • Opening write access for the PyPI package, how to do that? It would be nice for any of us to be able to update it when needed, rather than having to wait for me on that.

Is there something specific you'd like to add to Pyan, or is just keeping it alive enough for you for now?

Once we have a plan, setting some milestones in the issue tracker would be nice. I think the next milestone could be simply Python 3.8 support and an updated README. Thoughts?

Breaking the fork

As this repo has developed a fair bit compared to davidfraser/pyan, I suggest we cut the fork.

IndexError in visit_AnnAssign

Hi.

trying to run pyan against my code, it crashes with this error:

pyan3 *.py --uses --no-defines --colored --grouped --annotated --svg >myuses.svg
Traceback (most recent call last):
  File "/home/<user>/.local/bin/pyan3", line 11, in <module>
    sys.exit(main())
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/main.py", line 235, in main
    v = CallGraphVisitor(filenames, logger)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 75, in __init__
    self.process()
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 82, in process
    self.process_one(filename)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 96, in process_one
    self.visit(ast.parse(content, filename))
  File "/usr/lib64/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 340, in visit_Module
    self.generic_visit(node)  # visit the **children** of node
  File "/usr/lib64/python3.6/ast.py", line 261, in generic_visit
    self.visit(item)
  File "/usr/lib64/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 386, in visit_ClassDef
    self.visit(stmt)
  File "/usr/lib64/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 463, in visit_AsyncFunctionDef
    self.visit_FunctionDef(node)  # TODO: alias for now; tag async functions in output in a future version?
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 454, in visit_FunctionDef
    self.visit(stmt)
  File "/usr/lib64/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/home/<user>/.local/lib/python3.6/site-packages/pyan/analyzer.py", line 752, in visit_AnnAssign
    get_ast_node_name(value[0]),
IndexError: list index out of range

Cannot share my code, but if I could do something else to help you in debugging, just let me know

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.