An extremely fast Python linter, written in Rust.
Project description
ruff
An extremely fast Python linter, written in Rust.
Linting the CPython codebase from scratch.
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via
pip
- 🤝 Python 3.10 compatibility
- 🛠️
pyproject.toml
support - 📦 ESLint-inspired cache semantics
- 👀 TypeScript-inspired
--watch
semantics
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.
Read the launch blog post.
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
ruff also works with Pre-Commit (requires Cargo on system):
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.24
hooks:
- id: lint
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
An extremely fast Python linter.
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
.
See: .github/workflows/release.yaml
.
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 1 \
"./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 against the ecosystem's existing tools:
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
"pylint --recursive=y resources/test/cpython/" \
"pyflakes resources/test/cpython" \
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
"pycodestyle resources/test/cpython" \
"pycodestyle --select E501 resources/test/cpython" \
"flake8 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:
- ruff
- Pylint
- PyFlakes
- autoflake
- pycodestyle
- pycodestyle, limited to the checks supported by ruff
- Flake8
- Flake8, limited to the checks supported by ruff
- Flake8, with a hack to enable multiprocessing on macOS
- Flake8, with a hack to enable multiprocessing on macOS, limited to the checks supported by ruff
(You can poetry install
from ./scripts
to create a working environment for the above.)
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
Time (mean ± σ): 469.3 ms ± 16.3 ms [User: 2663.0 ms, System: 972.5 ms]
Range (min … max): 445.2 ms … 494.8 ms 10 runs
Benchmark 2: pylint --recursive=y resources/test/cpython/
Time (mean ± σ): 27.211 s ± 0.097 s [User: 26.405 s, System: 0.799 s]
Range (min … max): 27.056 s … 27.349 s 10 runs
Benchmark 3: pyflakes resources/test/cpython
Time (mean ± σ): 27.309 s ± 0.033 s [User: 27.137 s, System: 0.169 s]
Range (min … max): 27.267 s … 27.372 s 10 runs
Benchmark 4: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
Time (mean ± σ): 8.027 s ± 0.024 s [User: 74.255 s, System: 0.953 s]
Range (min … max): 7.969 s … 8.052 s 10 runs
Benchmark 5: pycodestyle resources/test/cpython
Time (mean ± σ): 41.666 s ± 0.266 s [User: 41.531 s, System: 0.132 s]
Range (min … max): 41.295 s … 41.980 s 10 runs
Benchmark 6: pycodestyle --select E501 resources/test/cpython
Time (mean ± σ): 14.547 s ± 0.077 s [User: 14.466 s, System: 0.079 s]
Range (min … max): 14.429 s … 14.695 s 10 runs
Benchmark 7: flake8 resources/test/cpython
Time (mean ± σ): 75.700 s ± 0.152 s [User: 75.254 s, System: 0.440 s]
Range (min … max): 75.513 s … 76.014 s 10 runs
Benchmark 8: flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython
Time (mean ± σ): 75.122 s ± 0.532 s [User: 74.677 s, System: 0.440 s]
Range (min … max): 74.130 s … 75.606 s 10 runs
Benchmark 9: python -m scripts.run_flake8 resources/test/cpython
Time (mean ± σ): 12.794 s ± 0.147 s [User: 90.792 s, System: 0.738 s]
Range (min … max): 12.606 s … 13.030 s 10 runs
Benchmark 10: python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501
Time (mean ± σ): 12.487 s ± 0.118 s [User: 90.052 s, System: 0.714 s]
Range (min … max): 12.265 s … 12.665 s 10 runs
Summary
'./target/release/ruff ./resources/test/cpython/ --no-cache' ran
17.10 ± 0.60 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
26.60 ± 0.96 times faster than 'python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501'
27.26 ± 1.00 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
30.99 ± 1.09 times faster than 'pycodestyle --select E501 resources/test/cpython'
57.98 ± 2.03 times faster than 'pylint --recursive=y resources/test/cpython/'
58.19 ± 2.02 times faster than 'pyflakes resources/test/cpython'
88.77 ± 3.14 times faster than 'pycodestyle resources/test/cpython'
160.06 ± 5.68 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython'
161.29 ± 5.61 times faster than 'flake8 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.24-py3-none-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fcdc7fa3563bb58bead73726222db20e813130a5d953ed0dafb7c6a873da07b4 |
|
MD5 | 23263dbe4075130e7880bce4367bd617 |
|
BLAKE2b-256 | 849d18c65ad0865bb7dd82575a7c6cd8fcd6f43516819ddd0bba3ed5035c7e81 |
Hashes for ruff-0.0.24-py3-none-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fccf093d14de15b90a77f366e011874e482f578f936a91a39c0cbf1704e329c7 |
|
MD5 | eaa492fdf9570a49132167a434943632 |
|
BLAKE2b-256 | 1459adefb1328341d72ce101eecf86fecf3f177e6b1f5fc461b3daf30b36f665 |
Hashes for ruff-0.0.24-py3-none-musllinux_1_2_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 135620cc9ab270ef15a8d7c6117c18a8ff431ddefc6c629e838b9d482b4ffa39 |
|
MD5 | 54c8b9ec93a60611c1d30a2de62ab9e1 |
|
BLAKE2b-256 | ad0fe074ca03f6a4e5493e1a8b508c553e358908cb844a4e9c202ae19bbd26be |
Hashes for ruff-0.0.24-py3-none-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e83337ab097acff2438e7837f01479c98040b4e5e85d865ce0bb72d6fc25cdaa |
|
MD5 | d713a987992d65ed5978cf42eca66d22 |
|
BLAKE2b-256 | 804a25038a98f4a9ba9fad5abc8a9fcff91c77e933901f85daa67162cccef384 |
Hashes for ruff-0.0.24-py3-none-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 817fc70a55ee10d72147901bf9cc0bb432845f3e408ac028d9d90c70377cce1c |
|
MD5 | 23b241f44a9502903a1d2556120e8da1 |
|
BLAKE2b-256 | c092822d6c45b40758dbb22fc7ad80454bd6b9c1a2b1047cc7eecd35331b26e1 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a948bb3ddbb6ffa97a9e6ad364c5707817c53890ffc8e2090b5eca8206246634 |
|
MD5 | 0d224958d399aecc954bd691673bc83b |
|
BLAKE2b-256 | 5ff94b883cb94b301702c668cca5dd6831b667429279c7a4dab533dd975b2560 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a3cb9531bc7fa58b5d64c920f20fda4d31c099835b67d187ebd5230ad23a6bb5 |
|
MD5 | f8dbbdff04c34e32e4d124b32081727c |
|
BLAKE2b-256 | 12551ff945ec4e480d8fa477cfefb873139ec55ab0edd3efa4016f77e832d677 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ce84a674e3945d053127f251279ab730937fa5b356b56fd76cb033f597bf0962 |
|
MD5 | d35f2f91aab41f1600d7a0ad2abcfa0c |
|
BLAKE2b-256 | 0e8178114d9512dcb573122518c0dc9123726b6a0c91cac531518cea30b17153 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | dc7e7e087509da6d4b7aad49616906ae4cabf8173ca5993d8d3c6e038941ef54 |
|
MD5 | 20f80452d161f8440e5ffa1a24a1513c |
|
BLAKE2b-256 | 6231fab32df5090bacc106c56963167d896594d787d1ac3218447f809d8bca13 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c74ede2a272229af9fddbf7dc2fc6e7653a6423abf0f9f2017fe4fdd5f855200 |
|
MD5 | 412c0eaa84089d94e474a447b1768f8e |
|
BLAKE2b-256 | bf1e78bc7c30ada8d1379f37329f51a78e8897c88db4ca7651deb6f6976ae705 |
Hashes for ruff-0.0.24-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ec099bf4a147b917bb870335557e734855a7d4a3b745065e2d5fe4659fca20b2 |
|
MD5 | 8b3c7855754099138e089baba76c13f1 |
|
BLAKE2b-256 | e3525a44217c324bc64924b9c43b8fa9d7bd134a8bf53ba0a5d406d37b467bff |
Hashes for ruff-0.0.24-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8436a8b76e276f32768ece4b9d41ffda9600c4edb4f31d018d545590ecc3f7c5 |
|
MD5 | 5c8aa94a9c8ba0c2d3c716481f2a280b |
|
BLAKE2b-256 | 6acc009fe06114b3d1a399cde52b179b06a2b3c531a1a13f54e797637e2277b2 |
Hashes for ruff-0.0.24-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 52df360a633335bf62d8e039e6f764c52fbeae2363cefb7287ffaa3b32c9cf50 |
|
MD5 | 44e639bb558b249666961b87092bb2cd |
|
BLAKE2b-256 | 889bd626ee9c1ec36c0e962efce568a81c5f29684a6e1f1b10e6970ba6275946 |
Hashes for ruff-0.0.24-py3-none-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7f9f459031b12121d692052175b9269beceaec85f808ea55b21413129fe4de07 |
|
MD5 | 92e14426efe957a833de06df9b447f02 |
|
BLAKE2b-256 | 90c6708c5d292fb2f300b6014c79c35670c1262a63c2bc66d2d616f583866687 |