Circular imports are one of those Python problems that stay invisible until a specific import order triggers an ImportError or a half-initialized module in production. They also quietly tighten the coupling in your codebase.
I wanted something that catches them before runtime, so I wrote knot — a small, dependency-free CLI.
How it works
knot never imports or runs your code. It parses each file with the standard-library ast module, maps every file to its fully-qualified module name, resolves both absolute and relative imports to internal modules, then finds cycles by computing strongly connected components with an iterative Tarjan's algorithm (safe on very large graphs). For each cycle it prints a concrete path like a -> b -> a.
Using it
pip install knot-imports
knot mypackage
knot mypackage --format mermaid # draws the import graph
It returns a non-zero exit code when cycles exist, so adding knot . to CI or a pre-commit hook keeps cycles from ever reaching main.
Why a static tool in the age of AI coding?
An AI assistant can spot a cycle if you ask it, but knot is deterministic, runs headless in CI on every commit in milliseconds, reasons over your entire import graph at once, and never sends your code anywhere. It's the cheap, reliable gate — the same reason linters and type checkers keep thriving.
It's open source (MIT), and early — feedback and contributions welcome: https://github.com/gazzycodes/knot

Top comments (0)