Skip to content

gazzycodes/knot

Repository files navigation

knot

CI Python License: MIT PyPI

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.

Demo

knot detecting a circular import in the bundled example project

The examples/shop package intentionally contains a circular import so you can try knot immediately: knot examples/shop.

Installation

pip install knot-imports

Or run from a checkout without installing:

PYTHONPATH=src python -m knot path/to/project

Usage

# 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 examples

Example

Given 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

Output formats

--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 highlighted

The Mermaid output pastes directly into a GitHub Markdown ```mermaid block or the Mermaid Live Editor.

Exit codes

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).

Use it as a pre-commit hook

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: knot

Then pre-commit install once, or run it on demand with pre-commit run knot --all-files.

Use it in CI (GitHub Actions)

- 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.

How it works

  1. Discover every .py file under the target and map it to its fully-qualified module name, mirroring how Python would import it.
  2. Parse each file with ast and resolve import / from ... import statements (absolute and relative) to internal modules; external imports are ignored.
  3. 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.

Development

git clone https://github.com/gazzycodes/knot
cd knot
PYTHONPATH=src python -m unittest discover -s tests -v

The demo GIF is regenerated with python make_demo.py (requires pillow).

Contributions are welcome — please open an issue or PR.

License

MIT — see LICENSE.

About

Detect and visualize circular imports in Python projects — fast, static, zero-dependency CLI.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages