Prior art

Reinventing the wheel graph

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:

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!