If you said "Louis, graphviz
is hardly secret and module imports are a pretty obvious graph to
plot, no way this hasn't been done" then you'd be right, but I found them lacking by themselves.
However, it was clear that a couple of them would be a decent start to piece together a solution with minimal development, or just to suggest good designs.
Best CLI: impgraph
The impgraph
package has as simple intuitive interface as you could hope for.
You add -j
to output JSON (and specify a filename), and -i
to change the image filename
(default is graph.png
). Unfortunately all I got for my module graph was one 'hop', i.e. no
recursion. However since you get JSON, you could do the recursion yourself by cross-referencing the
module names against the modules within the package.
It seems buggy even from very initial usage. I tried running it on the still very bare bones package
I began writing, and while it correctly listed some imports as []
, for the cli
and graph
modules it listed the first dependency for each correctly but then an empty string.
{
"__init__": {
"imports": []
},
"log_utils": {
"imports": []
},
"cli": {
"imports": [
"argcomplete",
""
]
},
"graph": {
"imports": [
"import_deps",
""
]
}
}
Most thorough namespace traversal: import_deps
The import_deps
package gives the concept sufficient
structure to work with, is tested, and under active maintenance (4 months ago at time of writing).
A set of modules makes a ModuleSet
, and it exposes a function ast_imports
that parses the
imports into a 4-tuple of module name (None if not a "from" import), [imported] object name,
as name (None if not used), and level of relative import (None if plain).
I've coded something similar to this, but am more than happy to reuse someone else's dedicated, tested, and maintained code.
That said, the mod_imports()
method exposed on the ModuleSet
object only appears to register
intra-module imports if they're explicitly specified as such (by full package paths or dotted
imports). So for a module a.py
that imports module b.py
in a package named foo
:
import b
would not be acknowledged, as it's presumed a global moduleimport foo.b
would be acknowledged, as it's a full package path import (i.e. the qualname)import .b
would be acknowledged, as it's a "dotted import" (i.e. relative import)
This isn't ideal, but is sufficient for a prototype. (It's often perfectly valid to just import the name with no package qualname or dot preceding it.)
Licensing
The above two libraries are MIT licensed, which is the same license I tend to use for projects like this, and so the wheels are in motion!
This post is the 1st of Mapping inter-module imports with importopoi, a series covering how I developed a library to depict the graph of module imports for any Python package (or set of scripts) for clarity about how package computation is structured at a glance, and subsequent ease of jumping in to edit or review a particular part. Read on for discussion of Recursive module graphs