Welcome, guest | Sign In | My Account | Store | Cart

This is a recipe to generate a diagram of dependencies of a script. It uses Python's modulefinder to get the dependencies, the two scripts available from http://www.tarind.com/depgraph.html to generate a dot file, and graphviz to convert the dot file to PNG. It also filters out a lot of noise and facilitates configurability.

Python, 99 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import sys, os
from tempfile import mkstemp
from getopt import getopt, GetoptError
from subprocess import call as oscall
from depgraph2dot import pydepgraphdot
from py2depgraph import mymf


def genDepGraph(inScript, options):
    path = sys.path[:]
    debug = 0
    mf = mymf(path, debug, options.exclude)
    mf.run_script(inScript)
    
    # find all modules in standard lib and set them aside for later;
    # assume that modules that don't have a filename are from stdlib
    ignore = set()
    for moduleName, module in mf.modules.iteritems():
        ign = False
        if options.ignoreNoFile and (not module.__file__):
            ign = True
        if options.ignoreStdlib and module.__file__:
            path1 = os.path.abspath(os.path.dirname(module.__file__)).lower()
            path2 = os.path.abspath('c:\python24\lib').lower()
            if path1 == path2:
                ign = True
        if ign:
            ignore.add(moduleName)
            
    return dict(depgraph=mf._depgraph, types=mf._types), ignore


class MyDepGraphDot(pydepgraphdot):
    def __init__(self, buffer, ignore=None):
        self.__depgraph = buffer['depgraph']
        self.__types = buffer['types']
        tmpfd, tmpname = mkstemp('.dot', 'depgraph_')
        os.close(tmpfd)
        self.__output = file(tmpname, 'w')
        self.__output_name = tmpname
        self.__ignore = ignore or set()
        #print 'Will ignore modules:', self.__ignore
        
    def toocommon(self, s, type):
        if s in self.__ignore:
            return 1
        return pydepgraphdot.toocommon(self, s, type)
    
    def get_data(self):
        return self.__depgraph, self.__types
    def get_output_file(self):
        return self.__output
    def get_output_name(self):
        self.__output.close()
        return self.__output_name


class Options:
    def __init__(self):
        self.exclude = []        # list of module names to exclude from analysis
        self.ignoreStdlib = True # modules from Python's stdlib will not be in graph
        self.ignoreNoFile = True # modules that don't have an associated file will not be in graph
        self.dotPath = r'C:\Program Files\Graphviz2.16\bin\dot'
        self.args = sys.argv[1]

        print 'Will %sinclude stdlib modules' % (self.ignoreStdlib and 'NOT ' or '')
        print 'Will %sinclude "file-less" modules' % (self.ignoreNoFile and 'NOT ' or '')
        print 'Will exclude the following modules and anything imported by them:', self.exclude
        print 'Will use "%s" as dot' % self.dotPath
    

# Process command line args
options = Options()

# start processing script for dependencies
inScript = options.args[0]
try:
    buffer, ignore = genDepGraph(inScript, options)
except IOError, exc: 
    print 'ERROR:', exc
    sys.exit(-1)
    
if not buffer['depgraph']:
    print 'NO dependencies of interest! Nothing to generate, exiting.'
    sys.exit()
    
dotdep = MyDepGraphDot(buffer, ignore)
dotdep.main( sys.argv )

# convert to png
basename = os.path.splitext(os.path.basename(inScript))[0]
pngOutput = basename + '_depgraph.png'
print 'Generating %s from %s' % (pngOutput, dotdep.get_output_name())
oscall([
    options.dotPath, '-Tpng', '-o', pngOutput, dotdep.get_output_name()]
    )

# cleanup
os.remove(dotdep.get_output_name())

Showing dependencies is useful when documenting the design of a package/module. Due to the dynamic nature of Python, just grepping for import statements is not a reliable method of determining dependencies. Some handy scripts exist at http://www.tarind.com/depgraph.html but they are not easy to use on Windows, and not easy to configure. The above recipe uses the two scripts as modules to make it trivial to generate the dependencies graph, and also adds configurability.

To use it: - download the two Python scripts from http://www.tarind.com/depgraph.html and install them in your path (e.g. Python's Scripts folder) - install graphviz - save the above recipe as a Python script, put in your path - verify that the path to dot is correct (Options.dotPath) - open a command shell, cd to the folder containing the script you want to analyse - run the recipe on that file, e.g. c:\Python24\Scripts\depgraph.py main.py

Note that I removed the command line parsing part of the code, in the Options.__init__() just before the print statements.

2 comments

Chad Cooper 16 years, 4 months ago  # | flag

error. I get this error, as if its not parsing sys.argv[1] properly??

F:\GIS\Projects\Land>depgraph.py ArkomaLand.py

Will NOT include stdlib modules

Will NOT include "file-less" modules

Will exclude the following modules and anything imported by them: []

Will use "C:\Program Files\Graphviz2.16\bin\dot" as dot

ERROR: [Errno 2] No such file or directory: 'A'

Chad Cooper 16 years, 4 months ago  # | flag

fixed it. changed:

inScript = options.args[0]

to:

inScript = options.args