Find unreferenced code with a code graph
By Steven Van ·
Graphify's Code findings list unreferenced functions, and its guide shows which of them are public APIs, overrides or special methods to keep.
Graphify now tracks unreferenced code on each indexed repository, as part of its Code findings checks. The feature has been available since September 27, and a new guide from Graphify explains how to read the results and which candidates are safe to remove.
Finding candidates
In Graphify, index the repository, open its Code findings and read the analysis details. These describe the checks as looking for "potentially unused code and matching function bodies", and advise reviewing the source before making a change.
The guide also gives a Python script for querying the graph directly. It counts incoming edges per node, using the relations calls, references, imports, imports_from, uses, inherits, re_exports and indirect_call. It keeps callable nodes with none, and skips anything under a tests path, since test functions are called by the runner. To run it yourself, install with uv tool install graphifyy, run graphify update ., then run the script against graphify-out/graph.json.
On a graph of the itsdangerous library, the script printed 22 candidates.
Why unreferenced is not dead
A graph only sees references written in the repository, so live code can show no incoming edges. Graphify's guide sorts six of the itsdangerous results:
- Public API: .loads() and .dumps() are called by users in other projects.
- Properties: .secret_key() is read as an attribute, not called.
- Special methods: .__str__() runs when Python formats the exception.
- Overrides: .load_payload() in url_safe.py overrides the base serializer, and calls go through self typed as the base class.
- Values passed by name: _lazy_sha1() is used as staticmethod(_lazy_sha1) in signer.py, which the graph does not record as an incoming dependency.
What is safe to remove
Candidates that remain after those exclusions are private, non-special, non-overriding functions that are not used as values or exported. For these, the guide suggests searching the name across the repository and any repository that imports it, running the tests, and checking the history for why it was added. None of the six itsdangerous examples survived those checks. A multi-repo graph can help with the cross-repository step.
Dynamic languages work too, but with more false positives, since reflection and string-built names leave no edges.