A Rust reimplementation of pylint's error checking that produces byte-for-byte identical output to pylint — 15-84x faster
Project description
prylint
A Rust reimplementation of pylint's error checking that produces byte-for-byte identical output to pylint — 15–84× faster.
prylint is not "inspired by" pylint. It is a bug-for-bug port: the same messages, at the same lines and columns, with the same text, in the same order, with the same exit codes — verified byte-identically against real pylint on 27 production codebases (~105,000 messages across ~60,000 Python files), including django, numpy, pandas, sympy, home-assistant, sqlalchemy, twisted, and pylint's own functional test suite. Where pylint has bugs, prylint reproduces them. Where pylint crashes, prylint reports the same crash message.
Install
pip install prylint
Requirements: a python3 (≥3.9) on PATH (used only to mirror pylint's
module-resolution paths and to reproduce CPython's exact syntax-error
messages for unparseable files). pylint and astroid themselves are not
required.
Usage
prylint targets pylint's errors-only mode. Use it exactly like pylint:
prylint . -E --disable=C0301,W0511,E0110,E0401,E0611,E1101,...
Output, message order, and exit codes match
pylint . -E --disable=... (pylint 4.0.5).
Benchmarks
Measured against pylint 4.0.5 (single run, Apple M-series, 10 cores — prylint currently uses one core; parallel mode is in development):
| codebase | pylint | prylint | speedup |
|---|---|---|---|
| home-assistant (17.5k files) | 1357s | 16.2s | 84× |
| airflow | 286s | 7.2s | 40× |
| salt | 183s | 4.3s | 43× |
| sentry | 323s | 8.6s | 38× |
| pandas | 259s | 7.6s | 34× |
| numpy | 194s | 7.2s | 27× |
| scikit-learn | 128s | 5.0s | 26× |
| matplotlib | 65s | 2.9s | 23× |
| django | 109s | 5.4s | 20× |
| sqlalchemy | 84s | 4.2s | 20× |
| zulip | 61s | 3.0s | 20× |
| twisted | 44s | 2.1s | 21× |
| mypy | 42s | 2.1s | 20× |
| nova (OpenStack) | 124s | 6.6s | 19× |
| sympy | 174s | 11.1s | 16× |
| …and 12 more, all ≥15× | |||
| total (27 repos) | 3617s | 103s | 35× |
Every row above is also an accuracy test: each repo's full output is byte-identical to pylint's.
How accuracy is verified
prylint was built by differential testing against pinned pylint 4.0.5 / astroid 4.0.4 / CPython 3.12:
- AST fidelity — prylint's parse tree (built on the ruff parser) is compared node-by-node against astroid's (positions, scopes, locals tables, brain transforms) across all corpus files: zero differences.
- Inference fidelity — astroid's inference engine (the part that powers
checks like
not-callableandno-value-for-parameter) is ported exactly: lazy generator semantics, the 100-result cap, cache-eviction order,Uninferablepropagation. Verified by dumping the inference result of every name/attribute/call node in every corpus file and comparing against astroid: byte-identical. - Output fidelity — full runs compared byte-for-byte, including message
order, module headers,
# pylint:pragma handling (disable/enable blocks,disable-next,skip-file), and exit-code bitmasks. - Blind testing — two batteries of 10 repos each were added after development and judged cold; every divergence was root-caused and fixed.
Determinism notes (where pylint disagrees with itself)
Two corners of pylint's output are nondeterministic between its own runs; prylint pins them:
- Crash reports (
F0002) embed a wall-clock filename (pylint-crash-<timestamp>.txt). prylint emits the same message shape with its own timestamp. - A few multi-message orderings iterate Python
sets, so they depend onPYTHONHASHSEED. prylint reproduces CPython's iteration order forPYTHONHASHSEED=0exactly (verified by fuzzing against the real interpreter), so it matchesPYTHONHASHSEED=0 pylint …byte-for-byte.
Scope and limitations
- Errors-only mode: prylint implements
pylint -E(all E/F checks exceptE0110,E0401,E0611,E1101) plus the message-control machinery (--disable, inline pragmas — including pragmas that re-enable messages locally). W/R/C checkers are not implemented except where inline pragmas in real code resurrect them. - Pinned semantics: behavior matches pylint 4.0.5 / astroid 4.0.4 running on CPython 3.12. Newer Python syntax (3.13/3.14) is reported as a syntax error, exactly as that pylint would.
- Config files are not read:
--rcfileis accepted but ignored; pyproject.toml/pylintrc discovery is not implemented yet. Pass options on the command line. - Linted-code dependencies do not need to be installed (matching how the
benchmark ground truth was produced). If your pylint runs with your
project's full virtualenv on
sys.path, setPRYLINT_PYTHONto that venv's interpreter for identical import resolution.
How it works
- File discovery, message control, and reporting are direct ports of
pylint's own logic (down to
os.walkordering and the************* Moduleheader rules). - Parsing uses ruff's Rust parser, then rebuilds astroid's exact tree shape (docstring extraction, decorator positions, implicit class locals, metaclass handling, brain transforms for dataclasses/enums/namedtuples…).
- A full port of astroid's inference engine resolves names, calls, attributes, MROs, and operator protocols with astroid's exact conservatism — including its caches and their quirks, because the quirks are observable in the output.
- Files the Rust parser rejects are re-judged by CPython itself (an
embedded, stdlib-only helper) so syntax-error messages match
ast.parseexactly.
The target invocation
prylint's accuracy contract is defined against this exact command:
pylint . -E --disable=C0301,W0511,C0114,R0402,C0116,R0914,W0718,R1735,W0105,R1705,W0603,W0104,C0209,W0719,C0411,C0412,R0912,R0915,C0413,C0115,C0103,W0613,R0801,W0602,R0913,R0917,W0622,R0902,R0911,R0913,R1702,R1716,W0212,R1728,C0121,R0916,C0415,W1401,C0206,C0302,R0904,W1514,R0903,E0110,R1714,W0707,R1718,W1309,W1203,E0611,W0611,W0108,W0177,E1101,C1803,R1721,W0123,R1720,R1710,W0221,W0122,C0201,W1510,R1729,R1737,C0325,R0401,E0401
Development
The differential-testing harness (corpus ground truth, AST/inference dump
comparators, per-code diff triage) lives in harness/. The accuracy
contract: any change must keep all 27 corpora byte-identical.
License
GPL-2.0-or-later, the same license as pylint itself — prylint reproduces pylint's message texts and behavior verbatim.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 prylint-0.2.1.tar.gz.
File metadata
- Download URL: prylint-0.2.1.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7e1f712d53c7ba0fa6c51e4d074a2598b62e779459b1d258c5376d809caeffc
|
|
| MD5 |
57f4bc0dccc730b35969ed1a55ca443e
|
|
| BLAKE2b-256 |
389d860b4cecf0a28622b09a6224e0dc4c35956d25daecefc9b06e286f88d806
|
File details
Details for the file prylint-0.2.1-py3-none-win_amd64.whl.
File metadata
- Download URL: prylint-0.2.1-py3-none-win_amd64.whl
- Upload date:
- Size: 3.6 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4aaf507f4eb0feb91ac463b7fd2cc93815b1a713fc5d773c85e21cb986f03770
|
|
| MD5 |
6f27fa1e3f3ab4ec1875919a1fe5e5a9
|
|
| BLAKE2b-256 |
48831a95e53f0d9d622c716634d63d9d8770f7b38fa6e4d022d4f5bb1600f9a3
|
File details
Details for the file prylint-0.2.1-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: prylint-0.2.1-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 3.7 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b8694ece2f17f9c1069dcdd35d653058a0d5b1d93bd79db3a0a675c38603dc9
|
|
| MD5 |
04da95812286f40a183f6e37f1175628
|
|
| BLAKE2b-256 |
f472770db0d8b776060ff7a43f4b2007e45e33c8bcc2b12a950702ede3f3d8f7
|
File details
Details for the file prylint-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: prylint-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.8 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65eb738935abcc334f8b8d135a7665cc64be8cd5e4d73b58777a1e7e3bcaaaea
|
|
| MD5 |
79f65d49d7dd1fdc8d5188e8e4df46bd
|
|
| BLAKE2b-256 |
3918c45e9bf02ebea4d9132f29a216b9b47f97ff25166bf36226e22b66486206
|
File details
Details for the file prylint-0.2.1-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: prylint-0.2.1-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.6 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3be18f7091981b03223edcacece969d5ad3319918d9f24ab584774c7b3a3a85
|
|
| MD5 |
2c5b2c1cd7952ba3c4b4ba40ba60ac1c
|
|
| BLAKE2b-256 |
a2f98893cc65018e0d6ee640a4985311252b4f5430ad99a324fa5b1ebb91689b
|
File details
Details for the file prylint-0.2.1-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: prylint-0.2.1-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.7 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88d1ca26a8ae5791be543aed3bc612e81e65cde2e8b2748e0e86f6943a7a1124
|
|
| MD5 |
2dc27f0a70dc510fa9ba92dd472a2ed9
|
|
| BLAKE2b-256 |
08c9a04c9531b4b8c173a9bfc56dbee9274302790cd71e1891f37dea2fff5617
|