Skip to main content

Untangled for Python

The Python library and CLI of Untangled, published on PyPI as untangled. It scans a Python project into the pairs document defined by the shared spec, following the Python profile.

Untangled is a re-imagining of snakefood by Martin Blais, and this library is its most direct successor: it parses every file, collects its imports and resolves them against the project's import roots, without running any of its code. See Credit.

It gives the same analyses, HTML reports and DOT graphs as the PHP library for the same pairs document. See what they look like in the examples.

Install

Requires Python 3.11+, and nothing else: the scanner uses Python's own ast module.

pip install untangled          # or: pipx install untangled
untangled --version

utg is a short alias for untangled, and python -m untangled works too. Untangled reads the project you point it at and never imports or runs it, so it does not need to be installed into that project's virtualenv. It runs on Python 3.11 or newer, whatever Python the project targets: a file using syntax from a newer Python than the one running Untangled is read statement by statement, which finds the same imports except in the rare statement that still does not parse, and the scan says which files were read that way.

Commands

untangled scan    [<path>]               [scan options] [--format=json|snakefood] [--output=<file>]
untangled analyze [<path>|<pairs.json>]  [scan options] [filter options] [--output=<file>]
untangled report  [<path>|<pairs.json>]  [scan options] [filter options]
                                         [--format=html|dot] [--style=classic] [--title=<text>] [--output=<file>]

Each command runs the steps before it: analyze scans first, and report scans and analyzes first. analyze and report also accept a pairs document saved by scan (from this library or any other Untangled library), so a large project can be scanned once and analyzed many ways. untangled --help prints the same summary.

scan

Writes every file-to-file dependency of the project as a pairs document.

untangled scan /path/to/project                          # pairs document (JSON) to stdout
untangled scan /path/to/project --output=pairs.json      # ... to a file
untangled scan /path/to/project --dev                    # include tests
untangled scan /path/to/project --format=snakefood       # snakefood's format, for sfood-* tools

analyze

Writes cycles and coupling metrics as JSON (the analysis document): a summary, per-unit fan-in, fan-out and instability, and every dependency cycle with one example loop.

untangled analyze /path/to/project                          # per file
untangled analyze /path/to/project --cluster=namespace:2    # per package path, e.g. shop.billing
untangled analyze /path/to/project --cluster=folder:2 --exclude='src/shop/legacy/**'
untangled analyze pairs.json --cluster=namespace:2          # from a saved scan

What each number means, and why it matters, is in metrics.

report

Writes one self-contained HTML page meant for people who do not read code: a summary in plain words, an interactive map of the dependencies, each cycle written out as a loop, the parts to look at first, and every metric with a short explanation. It needs no network access to open.

untangled report /path/to/project --output=report.html
untangled report /path/to/project --cluster=namespace:2 --title="Billing" --output=report.html
untangled report /path/to/project --format=dot | dot -Tsvg > graph.svg
untangled report /path/to/project --format=dot --style=classic | dot -Tpng > graph.png

--format=dot writes a Graphviz file instead, with units in a cycle drawn in red. --style=classic draws the black and white look of snakefood's sfood-graph.

Options

Option Meaning
--dev Also scan tests: test/ and tests/ folders, test_*.py, *_test.py, conftest.py. Tests usually depend on everything, which hides the real structure.
--source=PATH Add a folder, relative to the project, to the import roots (repeatable), for code importable from a folder the scanner does not find by itself.
--venv=PATH Read installed packages from this virtualenv instead of the ones found in the project (repeatable).
--format=FORMAT scan: json (the pairs document, default) or snakefood. report: html (default) or dot.
--style=classic report --format=dot only: snakefood's black and white look.
--title=TEXT report only: the report's title. Defaults to the project name in pyproject.toml, else the folder name.
--cluster=STRATEGY analyze, report: group files into units. namespace:N keeps the first N segments of the module name (shop.billing.invoice → shop.billing with N=2), folder:N the first N folders, and rules:<file> reads <prefix> <cluster> lines from a file, the first matching path prefix wins. See clustering.
--include=GLOB, --exclude=GLOB analyze, report: keep or drop project files by path (repeatable, the last matching pattern wins). * stays within a folder, ** crosses folders.
--output=FILE Write to a file instead of standard output.

What it reads

  • Files: every .py file under the project folder, except virtualenvs, hidden folders, __pycache__, node_modules, site-packages, *.egg-info, and build/ and dist/ at the top. Tests only with --dev, unless real code imports them. Stubs (.pyi), Cython sources (.pyx) and compiled extensions (.so, .pyd) are import targets, but are not read.
  • Import roots: the project folder, src/ (the src layout), the folders pyproject.toml gives setuptools, and --source folders.
  • Packages: the project's virtualenv (any folder in it with a pyvenv.cfg, such as .venv/), read from each installed distribution's RECORD, so import yaml is pypi:pyyaml. Without one, an import matches a dependency declared in pyproject.toml or requirements*.txt only when the names are equal (import jaraco.abode matches a declared jaraco.abode).

Every import ends up as a project file, a standard library module (builtin:os), a package (pypi:requests) or an unresolved reference (unresolved:module:...). The scanner never guesses: an import of a module that does not exist is unresolved, never an edge to its parent package. The Python profile has the full rules.

Exit codes and warnings

0 on success, 1 when the project folder does not exist, a pairs document or rules file cannot be read, or the output cannot be written, 2 for a usage error. Warnings go to standard error and do not change the exit code: a file that does not parse (such as Python 2 code), files read statement by statement, a --source or --venv that does not exist, and unresolved imports of modules outside the project when no virtualenv was found.

Development

cd python
python -m pip install -e . --group dev
pytest
ruff check . && ruff format --check .

The tests include the shared conformance cases that have a python/ folder.

License

GPL-2.0-only, see LICENSE. This folder may port code from snakefood, which is GPL, so code here must never be copied into the MIT-licensed folders.

Metadata

Release files for untangled 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for untangled 1.0.0
File Size Uploaded Provenance
untangled-1.0.0.tar.gz 83.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for untangled 1.0.0
File Interpreter ABI Platform
untangled-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 147.4 kB

Release files / untangled-1.0.0.tar.gz

Download URL untangled-1.0.0.tar.gz
Size 83.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2be8299cb8a085581bff6fc7c462444825356835db37cc65489363cdbefa02fd
BLAKE2b-256 checksum
How to use checksums
55c25538a7327c94eb589c641a00ddb1afaa541864f4cad1c94acbfb981567b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / untangled-1.0.0-py3-none-any.whl

Download URL untangled-1.0.0-py3-none-any.whl
Size 63.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83963637a988d6ddd0e08fd3b40f9e2208cba52205d4ecfb00ef3a941d0aa723
BLAKE2b-256 checksum
How to use checksums
6d312fb911e6605e0dceb0cd63dda48a4ffb3fd29447c864aead7ed522c5a6be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for untangled 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for untangled-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page