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 that produces byte-for-byte identical output — 15–2300× faster (median ~85×).
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 and the same Your code has been rated
footer — verified byte-identically against real pylint on 27 production
codebases (~60,000 Python files), including django, numpy, pandas, sympy,
home-assistant, sqlalchemy, twisted, scikit-learn, 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
Use it exactly like pylint — full check mode is the default:
prylint . # all checks (like `pylint .`)
prylint -E . # errors only (like `pylint -E .`)
prylint --disable=C0114,... . # same --disable / --enable / inline pragmas
prylint -j 8 . # opt-in parallel mode (see "Parallelism" below)
Output, message order, exit codes, the score footer, --rcfile /
pyproject.toml discovery, init-hook, and # pylint: pragmas all match
pylint 4.0.5.
Parallelism
prylint is single-threaded by default in the checking phase, on purpose: the byte-identity guarantee depends on replicating astroid's process-global, order-sensitive inference cache exactly, which parallelizing would perturb. The default path is the one verified byte-identical to pylint.
-j N (alias --jobs N) is an opt-in parallel mode, mirroring pylint's
own -j:
- The serial default stays byte-identical.
-j 1, or no-j, runs the exact existing single-engine path. The parallel branch is taken only for-j N>1(or-j 0= auto = number of cores). -j Nmay differ from serial — just like pylint's-j. Each worker has its own thread-local inference cache and checks a fixedfile_index % Nslice of the files, so cache warmth differs and the cross-file "close" checks (R0801duplicate-code,R0401cyclic-import) see only one slice. On django (full mode, 10-core M-series), serial vs-j 8differs by ~938 message lines, ~99% of which are R0801/R0401.- It is deterministic per run. The partition is fixed (not work-stealing)
and outputs merge in file order, so the same input gives the same
-j Noutput every run — it just isn't equal to-j 1. - Speedup is modest and partition-bound. Each worker re-boots the full file
set (the cost of determinism), so on django the win is ~15.4s → ~13.7s at
-j 8(~1.1×); it grows on corpora where per-module inference dominates the boot cost.
Use the default when you need byte-identity to pylint; reach for -j N only
when the single-core run is long and exact parity is not required. Full
details and the divergence breakdown are in
LIMITATIONS.md §5.
Benchmarks
prylint . vs pylint . (both full check mode), pylint 4.0.5, Apple M-series,
single-threaded:
| codebase | pylint | prylint | speedup |
|---|---|---|---|
| black | 26.7 hr | 41s | 2328× |
| sentry | 3.7 hr | 24s | 546× |
| home-assistant (17.5k files) | 10.3 hr | 82s | 452× |
| airflow | 1.9 hr | 17s | 399× |
| salt | 1890s | 8.8s | 215× |
| zulip | 909s | 5.3s | 172× |
| django | 1524s | 10.1s | 150× |
| ansible | 419s | 2.9s | 143× |
| nova (OpenStack) | 1209s | 10.3s | 117× |
| fastapi | 116s | 1.0s | 120× |
| mypy | 367s | 3.9s | 95× |
| sqlalchemy | 614s | 7.1s | 87× |
| pandas | 1009s | 14.2s | 71× |
| scikit-learn | 613s | 9.6s | 64× |
| sympy | 1238s | 26s | 48× |
| …and 12 more, all ≥30× | |||
| aggregate (27 repos) | 45.8 hr | 4.9 min | ~560× |
Median per-repo speedup ~85×; the aggregate is higher because pylint's
duplicate-code check (R0801) is O(n²) and dominates on test-heavy repos like
black. These are single-core numbers — the default inference engine is
deliberately single-threaded to replicate astroid's order-sensitive global
cache exactly (see LIMITATIONS.md). An opt-in -j N mode
trades that byte-identity for cores (see Parallelism); the
byte-identical default is already 15–2300× pylint, so parallelism is rarely the
bottleneck.
Every row above is also an accuracy test: each repo's full output is byte-identical to pylint's (see exceptions in LIMITATIONS.md).
Accuracy
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, brain transforms) across all corpus files: zero differences.
- Inference fidelity — astroid's inference engine is ported exactly:
lazy-generator semantics, the 100-node inference budget, the bounded-LRU
caches (
lookup128,_metaclass_lookup_attribute1024) with their exact eviction, the 64-entry inference-tip FIFO,Uninferablepropagation. Every name/attribute/call node's inference is dumped and compared against astroid. - Output fidelity — full runs compared byte-for-byte, including message
order, module headers, the score footer,
# pylint:pragma handling (disable/enable blocks,disable-next,skip-file), config-file discovery, 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.
Known, documented exceptions (one obscure SQLAlchemy class; the deliberately
excluded no-member family; the places pylint is nondeterministic against
itself) are catalogued in LIMITATIONS.md.
How it works
- File discovery, message control, config parsing, and reporting are direct
ports of pylint's own logic (down to
os.walkordering, the************* Moduleheader rule, and the score-report footer). - 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/attrs/…).
- 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.
Reproducing the test suite
scripts/setup_corpora.sh clones all 27 corpora at pinned commits and builds
the pinned pylint/astroid ground-truth venv. The accuracy contract: every
change must keep the corpora byte-identical (harness/ holds the differential
comparators).
License
GPL-2.0-or-later, the same license as pylint — 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.4.0.tar.gz.
File metadata
- Download URL: prylint-0.4.0.tar.gz
- Upload date:
- Size: 1.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98ab7b9c0f47ec2e85df66cb858e717d0858bfeb5f8f056fbea30bc9b00be8ad
|
|
| MD5 |
7ac50cf927826125179531b9a7502d4d
|
|
| BLAKE2b-256 |
5575528e86e7d818e11e61c2f760ecd38fa697b8c80f515fee4f1d5bb162ea6a
|
File details
Details for the file prylint-0.4.0-py3-none-win_amd64.whl.
File metadata
- Download URL: prylint-0.4.0-py3-none-win_amd64.whl
- Upload date:
- Size: 4.2 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 |
fcf9b3db1c047bb35a63b21c5d413d61ce9bb29548b569e265bdab28f829b0f9
|
|
| MD5 |
54bd1a3035b68317dd253195316f72b7
|
|
| BLAKE2b-256 |
7c48384956e45e63e58c416a16b391f9cfaf8882932d1c7c9a19aa9770517201
|
File details
Details for the file prylint-0.4.0-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: prylint-0.4.0-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 4.3 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 |
dd2086098d03e8307e30d092923ae640ca9a37ed0ea15c3e31d354126529e719
|
|
| MD5 |
49e7df13b67485b32c26dfee322014cc
|
|
| BLAKE2b-256 |
f5da2f534464fe00d31df75bf4176f43ff824b67545f01df6a38eb1e96e93bf5
|
File details
Details for the file prylint-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: prylint-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 4.4 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 |
998711d9089e1fd906d2c5d9ed9e057830a31d7b3c425ff583babe4aa2271f06
|
|
| MD5 |
f50a8f95e1b40789305cbb5eb098bfb3
|
|
| BLAKE2b-256 |
216b20a40cdb1089b1a9a766102a582f63bcd127b20d7816d4c54c82a1d8d258
|
File details
Details for the file prylint-0.4.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: prylint-0.4.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.2 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 |
7099bbc5e1b9b6e5daf2775568af6897baedbbdb4dec9d5a38bd906ec57e8dd8
|
|
| MD5 |
932f00c854730a3bd0cd303c45c2cbc7
|
|
| BLAKE2b-256 |
c9811ffd7839c28a0b5ceff4ade14577a21075dcd0a43dacca8728da42cc0b87
|
File details
Details for the file prylint-0.4.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: prylint-0.4.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 4.3 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 |
275e85b53949151e224f96399c87e8b7709f0fddfd14a4b2aee69dfb469f1f51
|
|
| MD5 |
124adba01e2092da9304d96071376cc9
|
|
| BLAKE2b-256 |
87a1e5ef4f3f4416099d4642ad46001ae8c4837b2fcf9129b66dfc4049be5762
|