Detect and visualize circular imports in Python projects — fast, static, and dependency-free.
Circular imports are a common source of ImportError and "partially initialized
module" failures, and they quietly couple your code together. knot finds them
without importing or running a single line of your project: it parses the
source with the standard-library ast module, builds the internal module
dependency graph, and reports every cycle with a concrete example path.
It has zero runtime dependencies, runs in CI (non-zero exit on cycles), and can emit a Mermaid diagram of your import graph.
The
examples/shoppackage intentionally contains a circular import so you can tryknotimmediately:knot examples/shop.
pip install knot-importsOr run from a checkout without installing:
PYTHONPATH=src python -m knot path/to/project# Analyze the current directory
knot .
# Analyze a specific package or project
knot path/to/your_package
# Exclude directories (repeatable); common ones are skipped by default
knot . --exclude migrations --exclude examplesGiven a package where order and customer import each other:
$ knot shop
Analyzed 3 modules, 3 internal imports.
Found 1 import cycle:
1. shop.customer -> shop.order -> shop.customer
--format text (default), json, or mermaid:
knot . --format json # machine-readable: summary, cycles, full graph
knot . --format mermaid # a graph LR diagram with cycle nodes highlightedThe Mermaid output pastes directly into a GitHub Markdown ```mermaid block
or the Mermaid Live Editor.
| Code | Meaning |
|---|---|
0 |
No cycles found |
1 |
One or more cycles found |
2 |
Error (e.g. path not found) |
Pass --no-fail to always exit 0 (useful when you only want the report).
Add this to your project's .pre-commit-config.yaml and knot runs on every commit:
repos:
- repo: https://github.com/gazzycodes/knot
rev: v0.1.1
hooks:
- id: knotThen pre-commit install once, or run it on demand with
pre-commit run knot --all-files.
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: pip install knot-imports
- run: knot .The non-zero exit on cycles fails the job automatically.
- Discover every
.pyfile under the target and map it to its fully-qualified module name, mirroring how Python would import it. - Parse each file with
astand resolveimport/from ... importstatements (absolute and relative) to internal modules; external imports are ignored. - Detect cycles by computing strongly connected components with an iterative implementation of Tarjan's algorithm (safe on very large graphs), then extract a concrete example path for each cycle.
git clone https://github.com/gazzycodes/knot
cd knot
PYTHONPATH=src python -m unittest discover -s tests -vThe demo GIF is regenerated with python make_demo.py (requires pillow).
Contributions are welcome — please open an issue or PR.
MIT — see LICENSE.
