Dead-code detection for Python isn't new. Here's specifically what PragyaLint does differently from the tools you may already have.
Tools like ruff --select F401 or pyflakes catch
unused imports within a file, but they don't know whether the
file itself is ever imported by anything. vulture goes
further with a whole-project scan, but reports a confidence
percentage derived from name-usage heuristics rather than an
actual reachability proof.
PragyaLint builds a real module graph from your entry points and asks a structural question: can anything reach this module by following imports? If not, it's HIGH-confidence dead — not a percentage, a fact about your import graph.
Interpreters, plugin systems, and command-dispatch code call functions
by name at runtime — getattr(obj, name), a
{"cmd": handler} table, exec/eval.
None of that is a plain name reference, so most static analyzers either
miss it (false negative — they report nothing, silently missing real
usage) or, worse, confidently delete the "unused" function anyway.
PragyaLint specifically recognizes dispatch tables and
getattr() calls as real usage, and when it detects
patterns it can't resolve statically (a computed
getattr name, exec, globals()),
it downgrades remaining findings project-wide to LOW confidence and
refuses to delete anything without --force. See
Dynamic dispatch for the full
mechanism.
Pytest discovers test_*.py and conftest.py
files by walking the filesystem, not through any import statement in
your source. A tool that only looks at the import graph will always
see these as "unreachable" and propose deleting your entire test
suite. PragyaLint recognizes pytest's discovery convention and treats
these files as entry points automatically.
PragyaLint is built entirely on the Python standard-library
ast module. There's no third-party parser to pin, update,
or audit — install it and it works with whatever Python version is
running it.
To be fair about the trade-offs:
getattr(mod, cmd.lower())) is still undecidable by any
static tool, PragyaLint included — the LOW-confidence downgrade and
--force gate exist because of this, not instead of it.