PragyaLint 🔍
A static dead-code analyzer for Python. PragyaLint builds a module graph from your entry points, traces what's actually reachable, and reports unused files, exports, functions, imports, and circular imports — so you can keep your codebase lean.
Inspired by OptiPrune (a dead-code analyzer for TypeScript/JavaScript) — PragyaLint brings the same concept to Python.
Features
| Area | What is included |
|---|---|
| Project analysis | Entry discovery, module graph, reachability, import-cycle reporting |
| Python | .py files and packages, __init__.py, relative imports, dynamic imports |
| Dead code | Unreachable modules/files, unused public exports, unused functions/classes, unused imports, unused assignments |
| Confidence levels | Every finding is rated high, medium, or low so you know how much to trust it |
| Output | Human-readable terminal output, JSON reports, and SARIF for CI/code-scanning workflows |
| CI-ready | --fail-on <confidence> gates your pipeline on dead-code findings |
| Configuration | pragyalint.toml, pragyalint.json, or [tool.pragyalint] in pyproject.toml |
| Opt-in fixes | --fix actually removes dead code — confidence-gated with --dry-run |
| Zero dependencies | Uses only the Python standard library — no pip install deps, no parser to ship |
Installation
pip install pragyalint
# or
pipx install pragyalint
# or
pip install --user pragyalint
Requires Python 3.11+.
Quick start
Run an analysis from the project root:
pragyalint
Specify entry points and output formats:
pragyalint --entry src/main.py
pragyalint --json
pragyalint --sarif > pragyalint.sarif
Commands
| Command | Purpose |
|---|---|
pragyalint |
Analyze the project (default command) |
pragyalint --json |
Print the structured report as JSON |
pragyalint --sarif |
Print SARIF output for CI |
pragyalint --help |
Print command and option help |
pragyalint --version |
Print the version |
Analyze flags
| Flag | Description | Default |
|---|---|---|
-r, --root-dir <path> |
Root directory of the project | current directory |
-e, --entry <module...> |
Entry-point modules, globs, or file paths | auto-detect |
-i, --ignore <patterns...> |
Glob patterns to ignore | [] |
-x, --extensions <exts...> |
File extensions to analyze | .py |
--include <paths...> |
Only analyze files under these paths | [] |
--rules <rules...> |
Restrict analysis to specific rules | all |
--no-report-unused-exports |
Disable unused-export reporting | enabled |
--no-conventional-entries |
Exclude conventional entries (main.py, app.py, ...) |
included |
--include-entry-exports |
Report unused exports in entry files | disabled |
--ignore-tests |
Ignore test files and directories | disabled |
--cycles |
Report circular import cycles | disabled |
--fail-on <confidence> |
Exit non-zero at/above this confidence | — |
--json |
JSON output | — |
--sarif |
SARIF output | — |
--no-color |
Disable ANSI colors | — |
-v, --verbose |
Print internal graph state | — |
--fix <targets...> |
Apply fixes: files, imports, exports |
— |
--confidence <level> |
Minimum fix confidence: high, medium+, low+, all |
high |
--dry-run |
Log planned fixes without changing files | — |
--force |
Allow a fix considered unsafe (e.g. __all__-listed exports) |
— |
Rules / finding types
| Rule | Confidence | What it finds |
|---|---|---|
unused_file |
high |
Modules unreachable from any entry point |
unused_import |
high |
Imports never used in their module |
unused_export |
medium |
Public names never imported anywhere |
unused_local |
medium/low |
Functions/classes/variables defined but never used |
cycle |
low |
Circular import cycles |
Confidence
Every finding carries a confidence level:
- high — structurally certain (module unreachable, import unused)
- medium — strongly implied (export/definition not referenced)
- low — heuristic (constant assignments, cycles)
Use --fail-on to make your CI fail on a given threshold, e.g.:
pragyalint --fail-on high
This exits with a non-zero code when any high-confidence finding exists — perfect for
blocking PRs that introduce dead code.
Fixes
Like OptiPrune, PragyaLint's fixer is explicit, not implicit. Analysis only reports;
use --fix to actually remove dead code. Always start with a dry run, inspect the
output, then drop --dry-run.
# Preview what would change (does NOT modify files)
pragyalint --fix --dry-run
# Actually remove dead code
pragyalint --fix
# Remove dead code and functions with lower-risk edits too
pragyalint --fix exports --confidence medium+
| Target | What it does |
|---|---|
files |
Delete unreachable modules (never deletes entries or __init__.py) |
imports |
Remove unused imports; trims unused names from multi-name imports |
exports |
Remove unused function/class/variable definitions (respects __all__) |
Fixes are confidence-gated:
--confidence high(default) — only high-confidence removals.--confidence medium+— also remove unused exports/definitions.--force— allow an edit otherwise considered unsafe (e.g. removing a name listed in__all__).
Always commit before fixing. Removing code can have ripple effects. Run
--dry-runfirst and re-run after fixing.
Configuration
PragyaLint loads configuration from the first file it finds (starting at the root directory):
pragyalint.tomlpragyalint.json[tool.pragyalint]section ofpyproject.toml
# pragyalint.toml
entry = ["src"]
ignore = ["migrations/", "generated/"]
extensions = [".py"]
detect_cycles = true
fail_on = "medium"
Or inside your pyproject.toml:
[tool.pragyalint]
ignore = ["tests/"]
report_unused_exports = false
Why Python for analyzing Python?
PragyaLint is written in pure Python and relies on the standard-library ast module.
That gives you:
- A complete, battle-tested Python parser with zero external dependencies
- Correct handling of every modern Python syntax construct
- Simple distribution via PyPI /
pipx - No parser to build or maintain
Development
git clone https://github.com/example/pragyalint
cd pragyalint
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pragyalint-0.1.0.tar.gz.
File metadata
- Download URL: pragyalint-0.1.0.tar.gz
- Upload date:
- Size: 59.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a790fc60f86173299819afa093137e402e566b13652e04bf1d1784294ea5e2e
|
|
| MD5 |
e04fe5a3eba5a0801e55e717839e5b26
|
|
| BLAKE2b-256 |
1edeb7f290ba02f0c8d3113ffc2d743dc4f74295702bcdb8c55990ecae8dd8b5
|
File details
Details for the file pragyalint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pragyalint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 40.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
226c037e527ab94f7b6625323095c641849abfee3346ed1f7ecc3614d4062744
|
|
| MD5 |
e963477c120989d4fa71c1d4a5d2d439
|
|
| BLAKE2b-256 |
5ed24f2ede997e9ea617fe6ab37af24bd3644607d1bc584ccc05a7d760954373
|