A fast, Rust-powered CLI tool that detects first-party circular imports in Python projects.
Ouroboros indexes your Python source files, extracts import relationships, resolves them to first-party modules, builds a dependency graph, and uses Tarjan's SCC algorithm to find circular dependencies — all without running Python.
Designed for large monorepos with millions of lines of code.
- Discovers
.pyfiles across configurable source roots - Extracts
importandfrom ... importstatements (including relative imports) - Resolves imports against a first-party module index
- Builds a compact file-level dependency graph
- Detects circular dependencies via strongly connected components (SCCs)
- Configurable SCC size filtering, local-import inclusion, and source roots
- Human-readable output with cycles grouped by package and import line numbers
- JSON output (
--format json) for programmatic consumption - Ignore list support (
[[cycles.ignore]]in config) to suppress known cycles --dump-ignoresto bootstrap ignore lists from detected cycles--strictmode for CI enforcement (exit code 1 on cycles)--packageflag to filter to intra-package cycles only
See USAGE.md for full details on every flag and config option.
Requires Rust 1.85+.
cargo install --path crates/ouroboros-cliThe binary is called oboros.
Requires maturin and Python 3.8+.
maturin build --release
pip install target/wheels/ouroboros-*.whl- Create an
oboros.tomlin your Python project root:
source-roots = ["src"]- Run it:
oborosOuroboros automatically discovers oboros.toml by walking upward from the current directory. You can also point to a config explicitly:
oboros --config path/to/oboros.tomlAll CLI flags:
oboros [--config <FILE>] [--format human|json] [--package] [--dump-ignores] [--strict]
See USAGE.md for the full configuration reference and detailed usage instructions.
ouroboros/
├── crates/
│ ├── ouroboros-cli/ # CLI binary (oboros)
│ └── ouroboros-core/ # Core library
│ └── src/
│ ├── config.rs # Config loading & validation
│ ├── discovery/ # File discovery & module indexing
│ ├── parser/ # Python import extraction
│ ├── resolver/ # Import-to-module resolution
│ ├── graph/ # Dependency graph & SCC detection
│ └── cycles/ # Cycle filtering & reporting
├── fixtures/
│ ├── generate.py # Test fixture generator
│ └── sample_project/ # Generated sample project (git-ignored)
└── pyproject.toml # Python wheel build config
Ouroboros runs through six phases:
- Discovery — walks configured source roots, finds
.pyfiles, and maps each to a canonical module name (e.g.src/pkg/a.py→pkg.a) - Import extraction — parses each file with RustPython and extracts import statements
- Resolution — resolves raw imports against the first-party module index, classifying each as resolved, unresolved, or ambiguous
- Graph building — constructs a directed dependency graph from resolved edges
- Cycle detection — runs Tarjan's algorithm to find strongly connected components
- Reporting — filters SCCs by configured size bounds and prints results
cargo build # build all crates
cargo test # run tests
cargo run -p ouroboros-cli -- --config fixtures/sample_project/oboros.toml # run against fixturesThe fixture generator creates a sample Python project with known circular imports:
python fixtures/generate.py # default (~30 files)
python fixtures/generate.py --scale 10 # larger project (~280 files)
python fixtures/generate.py --seed 123 # reproducible randomnessTBD