An extremely fast Python linter, written in Rust.
Project description
ruff
An extremely fast Python linter, written in Rust.
Major features:
- 10-100x faster than your current linter.
- Installable via
pip
. - Python 3.10 compatibility.
- ESLint-inspired cache semantics.
- TypeScript-inspired
--watch
semantics. pyproject.toml
support.
ruff
is a proof-of-concept and not yet intended for production use. It supports only a small
subset of the Flake8 rules, and may crash on your codebase.
Installation and usage
Installation
Available as ruff
on PyPI:
pip install ruff
Usage
To run ruff
, try any of the following:
ruff path/to/code/to/check.py
ruff path/to/code/
ruff path/to/code/*.py
You can run ruff
in --watch
mode to automatically re-run on-change:
ruff path/to/code/ --watch
Configuration
ruff
is configurable both via pyproject.toml
and the command line.
For example, you could configure ruff
to only enforce a subset of rules with:
[tool.ruff]
line-length = 88
select = [
"F401",
"F403",
]
Alternatively, on the command-line:
ruff path/to/code/ --select F401 F403
See ruff --help
for more:
ruff
A Python linter written in Rust
USAGE:
ruff [OPTIONS] <FILES>...
ARGS:
<FILES>...
OPTIONS:
-e, --exit-zero Exit with status code "0", even upon detecting errors
-h, --help Print help information
--ignore <IGNORE>... Comma-separated list of error codes to ignore
-n, --no-cache Disable cache reads
-q, --quiet Disable all logging (but still exit with status code "1" upon
detecting errors)
--select <SELECT>... Comma-separated list of error codes to enable
-v, --verbose Enable verbose logging
-w, --watch Run in watch mode by re-running whenever files change
Development
ruff
is written in Rust (1.63.0). You'll need to install the Rust toolchain
for development.
Assuming you have cargo
installed, you can run:
cargo run resources/test/src
cargo fmt
cargo clippy
cargo test
Deployment
ruff
is distributed on PyPI, and published via maturin
:
maturin publish --skip-existing --target x86_64-apple-darwin && \
maturin publish --skip-existing --target aarch64-apple-darwin
Benchmarking
First, clone CPython. It's a large and diverse Python codebase, which makes it a good target for benchmarking.
git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpython
Add this pyproject.toml
to the CPython directory:
[tool.linter]
line-length = 88
exclude = [
"Lib/ctypes/test/test_numbers.py",
"Lib/dataclasses.py",
"Lib/lib2to3/tests/data/bom.py",
"Lib/lib2to3/tests/data/crlf.py",
"Lib/lib2to3/tests/data/different_encoding.py",
"Lib/lib2to3/tests/data/false_encoding.py",
"Lib/lib2to3/tests/data/py2_test_grammar.py",
"Lib/sqlite3/test/factory.py",
"Lib/sqlite3/test/hooks.py",
"Lib/sqlite3/test/regression.py",
"Lib/sqlite3/test/transactions.py",
"Lib/sqlite3/test/types.py",
"Lib/test/bad_coding2.py",
"Lib/test/badsyntax_3131.py",
"Lib/test/badsyntax_pep3120.py",
"Lib/test/encoded_modules/module_iso_8859_1.py",
"Lib/test/encoded_modules/module_koi8_r.py",
"Lib/test/sortperf.py",
"Lib/test/test_email/torture_test.py",
"Lib/test/test_fstring.py",
"Lib/test/test_genericpath.py",
"Lib/test/test_getopt.py",
"Lib/test/test_grammar.py",
"Lib/test/test_htmlparser.py",
"Lib/test/test_importlib/stubs.py",
"Lib/test/test_importlib/test_files.py",
"Lib/test/test_importlib/test_metadata_api.py",
"Lib/test/test_importlib/test_open.py",
"Lib/test/test_importlib/test_util.py",
"Lib/test/test_named_expressions.py",
"Lib/test/test_patma.py",
"Lib/test/test_peg_generator/__main__.py",
"Lib/test/test_pipes.py",
"Lib/test/test_source_encoding.py",
"Lib/test/test_weakref.py",
"Lib/test/test_webbrowser.py",
"Lib/tkinter/__main__.py",
"Lib/tkinter/test/test_tkinter/test_variables.py",
"Modules/_decimal/libmpdec/literature/fnt.py",
"Modules/_decimal/tests/deccheck.py",
"Tools/c-analyzer/c_parser/parser/_delim.py",
"Tools/i18n/pygettext.py",
"Tools/test2to3/maintest.py",
"Tools/test2to3/setup.py",
"Tools/test2to3/test/test_foo.py",
"Tools/test2to3/test2to3/hello.py",
]
Next, to benchmark the release build:
cargo build --release
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
"./target/release/ruff ./resources/test/cpython/"
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
Time (mean ± σ): 353.6 ms ± 7.6 ms [User: 2868.8 ms, System: 171.5 ms]
Range (min … max): 344.4 ms … 367.3 ms 10 runs
Benchmark 2: ./target/release/ruff ./resources/test/cpython/
Time (mean ± σ): 59.6 ms ± 2.5 ms [User: 36.4 ms, System: 345.6 ms]
Range (min … max): 55.9 ms … 67.0 ms 48 runs
To benchmark the ecosystem's existing tools:
hyperfine --ignore-failure --warmup 5 \
"pycodestyle resources/test/cpython" \
"pyflakes resources/test/cpython" \
"flake8 resources/test/cpython" \
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
"pylint --recursive=y resources/test/cpython/" \
"pycodestyle --select E501 resources/test/cpython" \
"flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython" \
"python -m scripts.run_flake8 resources/test/cpython" \
"python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501"
In order, these evaluate:
pycodestyle
pyflakes
flake8
autoflake
pylint
pycodestyle
, limited to the checks supported by Ruffflake8
, limited to the checks supported by Ruffflake8
, with a hack to enable multiprocessing on macOSflake8
, limited to the checks supported by Ruff, with a hack to enable multiprocessing on macOS
(You can poetry install
from ./scripts
to create a working environment for the above.)
∴ hyperfine --ignore-failure --warmup 5 \
→ "pycodestyle resources/test/cpython" \
→ "pyflakes resources/test/cpython" \
→ "flake8 resources/test/cpython" \
→ "autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
→ "pylint --recursive=y resources/test/cpython/" \
→ "pycodestyle --select E501 resources/test/cpython" \
→ "flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython" \
→ "python -m run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501" \
→ "python -m run_flake8 resources/test/cpython"
Benchmark 1: pycodestyle resources/test/cpython
Time (mean ± σ): 41.921 s ± 1.409 s [User: 41.451 s, System: 0.194 s]
Range (min … max): 41.182 s … 45.894 s 10 runs
Warning: Ignoring non-zero exit code.
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet PC without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 2: pyflakes resources/test/cpython
Time (mean ± σ): 27.960 s ± 1.251 s [User: 27.491 s, System: 0.236 s]
Range (min … max): 26.449 s … 29.899 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 3: flake8 resources/test/cpython
Time (mean ± σ): 75.320 s ± 0.909 s [User: 74.625 s, System: 0.610 s]
Range (min … max): 74.181 s … 77.336 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 4: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
Time (mean ± σ): 32.690 s ± 0.585 s [User: 32.300 s, System: 0.296 s]
Range (min … max): 31.948 s … 33.326 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 5: pylint --recursive=y resources/test/cpython/
Time (mean ± σ): 27.592 s ± 0.227 s [User: 26.627 s, System: 0.911 s]
Range (min … max): 27.325 s … 27.955 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 6: pycodestyle --select E501 resources/test/cpython
Time (mean ± σ): 14.540 s ± 0.156 s [User: 14.397 s, System: 0.121 s]
Range (min … max): 14.384 s … 14.920 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 7: flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython
Time (mean ± σ): 75.391 s ± 0.600 s [User: 74.642 s, System: 0.591 s]
Range (min … max): 74.458 s … 76.550 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 8: python -m run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501
Time (mean ± σ): 12.542 s ± 0.467 s [User: 87.891 s, System: 0.816 s]
Range (min … max): 11.771 s … 13.034 s 10 runs
Benchmark 9: python -m run_flake8 resources/test/cpython
Time (mean ± σ): 12.276 s ± 0.398 s [User: 86.720 s, System: 0.792 s]
Range (min … max): 11.809 s … 12.865 s 10 runs
Summary
'python -m run_flake8 resources/test/cpython' ran
1.02 ± 0.05 times faster than 'python -m run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501'
1.18 ± 0.04 times faster than 'pycodestyle --select E501 resources/test/cpython'
2.25 ± 0.08 times faster than 'pylint --recursive=y resources/test/cpython/'
2.28 ± 0.13 times faster than 'pyflakes resources/test/cpython'
2.66 ± 0.10 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
3.41 ± 0.16 times faster than 'pycodestyle resources/test/cpython'
6.14 ± 0.21 times faster than 'flake8 resources/test/cpython'
6.14 ± 0.21 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython'
License
MIT
Project details
Release history Release notifications | RSS feed
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
Hashes for ruff-0.0.16-cp310-cp310-macosx_11_0_arm64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 657070d63691b92cda091f679c5e798e2c557586fffb998aad59cd20d1c63287 |
|
MD5 | 68e740676b0afc2b160e702ef421d288 |
|
BLAKE2b-256 | eb644b0c17535653dc13f61d80ba941ae0dd4fe26f994ea986a1570d12f29ee0 |
Hashes for ruff-0.0.16-cp310-cp310-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 667e215f1f12d0bfc7bd8b8e093fea11f726a401ec848274b223ad1a0aa524ce |
|
MD5 | 38765863dd01ac7a16b844c18dcc4ac3 |
|
BLAKE2b-256 | 468f7ceae1a844515829d56f753dd486690364bcd90dcbbaa43d75957088a56d |
Hashes for ruff-0.0.16-cp39-cp39-macosx_11_0_arm64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7ae8a1922ab55671341ee24227ad44cccc5fb4b00876ba605eda84c29e4c0e7f |
|
MD5 | 5b299da3ea381825561f303ef4f96373 |
|
BLAKE2b-256 | d6760619047971e7907d180e7915f6378b69d1307f47817b7f8a0dfd2a9eeb37 |
Hashes for ruff-0.0.16-cp39-cp39-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8e1319a51bae170e3043dc13526c1c05e5811c789ba51719bf4b9740adddfd14 |
|
MD5 | 3b1928ca28f57d0542426f6e430e7c7c |
|
BLAKE2b-256 | b5db98c479526d94218c86bfbf380b4aafd6a6a87df5565d7780865b5286b628 |
Hashes for ruff-0.0.16-cp38-cp38-macosx_11_0_arm64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 06449a8296e1ffc57c34272dab24c2626d216281803151653619dde80d0e84c6 |
|
MD5 | de456aeaefce5885bf4c49f2d6526451 |
|
BLAKE2b-256 | 883d3d76ff7b201203cdd0c243e312e29c9bb5c07803d6aab9efa02540736a23 |
Hashes for ruff-0.0.16-cp38-cp38-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 617617fc1c18dfa640c6f34dfd053c471e45efc0cd0ae8c78d8c8f989e08c7d2 |
|
MD5 | bdb8469332ed067315085e72b4a94ad8 |
|
BLAKE2b-256 | a2f5358d76c6871f319c38c6afa31c554866c7ef825f7929adbca04753d01955 |
Hashes for ruff-0.0.16-cp37-cp37m-macosx_11_0_arm64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ccdd281b26a40efe716d72a90bb555648ca88409d133468561e8a1f9578656e7 |
|
MD5 | dace66035a5072e405f18e69629bb35f |
|
BLAKE2b-256 | fbd0355f90da44751f70d39efc35701f9ce2c8a7e87d8eef392fc8a034c94fd6 |
Hashes for ruff-0.0.16-cp37-cp37m-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 22994f188883a7e24bce42876ddd2aa2a3da8be5d24144a66a6fb2b02249a0c7 |
|
MD5 | 6339e1fa9fc4842133a12b288044a028 |
|
BLAKE2b-256 | 164785d7ed09a15e42791c57017f312b506fc9f87edf5c5876268c4128c402a4 |