Skip to main content

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 output15–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 N may differ from serial — just like pylint's -j. Each worker has its own thread-local inference cache and checks a fixed file_index % N slice of the files, so cache warmth differs and the cross-file "close" checks (R0801 duplicate-code, R0401 cyclic-import) see only one slice. On django (full mode, 10-core M-series), serial vs -j 8 differs 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 N output 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:

  1. 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.
  2. Inference fidelity — astroid's inference engine is ported exactly: lazy-generator semantics, the 100-node inference budget, the bounded-LRU caches (lookup 128, _metaclass_lookup_attribute 1024) with their exact eviction, the 64-entry inference-tip FIFO, Uninferable propagation. Every name/attribute/call node's inference is dumped and compared against astroid.
  3. 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.
  4. 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.walk ordering, the ************* Module header 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.parse exactly.

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

prylint-0.4.0.tar.gz (1.9 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

prylint-0.4.0-py3-none-win_amd64.whl (4.2 MB view details)

Uploaded Python 3Windows x86-64

prylint-0.4.0-py3-none-manylinux_2_28_aarch64.whl (4.3 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

prylint-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

prylint-0.4.0-py3-none-macosx_11_0_arm64.whl (4.2 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

prylint-0.4.0-py3-none-macosx_10_12_x86_64.whl (4.3 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

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

Hashes for prylint-0.4.0.tar.gz
Algorithm Hash digest
SHA256 98ab7b9c0f47ec2e85df66cb858e717d0858bfeb5f8f056fbea30bc9b00be8ad
MD5 7ac50cf927826125179531b9a7502d4d
BLAKE2b-256 5575528e86e7d818e11e61c2f760ecd38fa697b8c80f515fee4f1d5bb162ea6a

See more details on using hashes here.

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

Hashes for prylint-0.4.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 fcf9b3db1c047bb35a63b21c5d413d61ce9bb29548b569e265bdab28f829b0f9
MD5 54bd1a3035b68317dd253195316f72b7
BLAKE2b-256 7c48384956e45e63e58c416a16b391f9cfaf8882932d1c7c9a19aa9770517201

See more details on using hashes here.

File details

Details for the file prylint-0.4.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for prylint-0.4.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 dd2086098d03e8307e30d092923ae640ca9a37ed0ea15c3e31d354126529e719
MD5 49e7df13b67485b32c26dfee322014cc
BLAKE2b-256 f5da2f534464fe00d31df75bf4176f43ff824b67545f01df6a38eb1e96e93bf5

See more details on using hashes here.

File details

Details for the file prylint-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for prylint-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 998711d9089e1fd906d2c5d9ed9e057830a31d7b3c425ff583babe4aa2271f06
MD5 f50a8f95e1b40789305cbb5eb098bfb3
BLAKE2b-256 216b20a40cdb1089b1a9a766102a582f63bcd127b20d7816d4c54c82a1d8d258

See more details on using hashes here.

File details

Details for the file prylint-0.4.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for prylint-0.4.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7099bbc5e1b9b6e5daf2775568af6897baedbbdb4dec9d5a38bd906ec57e8dd8
MD5 932f00c854730a3bd0cd303c45c2cbc7
BLAKE2b-256 c9811ffd7839c28a0b5ceff4ade14577a21075dcd0a43dacca8728da42cc0b87

See more details on using hashes here.

File details

Details for the file prylint-0.4.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for prylint-0.4.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 275e85b53949151e224f96399c87e8b7709f0fddfd14a4b2aee69dfb469f1f51
MD5 124adba01e2092da9304d96071376cc9
BLAKE2b-256 87a1e5ef4f3f4416099d4642ad46001ae8c4837b2fcf9129b66dfc4049be5762

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page