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 support
- 🔧 ESLint-inspired
--fix
support - 👀 TypeScript-inspired
--watch
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.
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.29
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 (v0.0.29)
An extremely fast Python linter.
USAGE:
ruff [OPTIONS] <FILES>...
ARGS:
<FILES>...
OPTIONS:
-e, --exit-zero Exit with status code "0", even upon detecting errors
-f, --fix Attempt to automatically fix lint 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
Compatibility with Black
ruff is intended to be compatible with Black, and should be
compatible out-of-the-box as long as the line-length
setting is consistent between the two.
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing lint rules that are obviated by Black (e.g., stylistic rules).
Rules
Code | Name | Message |
---|---|---|
E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
E501 | LineTooLong | Line too long |
E711 | NoneComparison | Comparison to None should be cond is None |
E712 | TrueFalseComparison | Comparison to True should be cond is True |
E713 | NotInTest | Test for membership should be not in |
E714 | NotIsTest | Test for object identity should be is not |
E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
E902 | IOError | No such file or directory: ... |
F401 | UnusedImport | ... imported but unused |
F403 | ImportStarUsage | Unable to detect undefined names |
F541 | FStringMissingPlaceholders | f-string without any placeholders |
F631 | AssertTuple | Assert test is a non-empty tuple, which is always True |
F634 | IfTuple | If test is a tuple, which is always True |
F704 | YieldOutsideFunction | a yield or yield from statement outside of a function/method |
F706 | ReturnOutsideFunction | a return statement outside of a function/method |
F707 | DefaultExceptNotLast | an except: block as not the last exception handler |
F821 | UndefinedName | Undefined name ... |
F822 | UndefinedExport | Undefined name ... in __all__ |
F823 | UndefinedLocal | Local variable ... referenced before assignment |
F831 | DuplicateArgumentName | Duplicate argument name in function definition |
F841 | UnusedVariable | Local variable ... is assigned to but never used |
F901 | RaiseNotImplemented | raise NotImplemented should be raise NotImplementedError |
R001 | UselessObjectInheritance | Class ... inherits from object |
R002 | NoAssertEquals | assertEquals is deprecated, use assertEqual instead |
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/fixtures
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/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/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/test_fstring.py",
"Lib/test/test_grammar.py",
"Lib/test/test_importlib/test_util.py",
"Lib/test/test_named_expressions.py",
"Lib/test/test_patma.py",
"Lib/test/test_source_encoding.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.29-py3-none-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 23f73aa750ca3bb4a603775ce72fe2346e34bcca6110aa25f2ebc47586652e1f |
|
MD5 | dcc0cb4a3b0ed933cbcb336352129f0a |
|
BLAKE2b-256 | 4d8746a4cbeec2442df0c6f3c6a55487539d158dd47f6042ed93a3bdf1259057 |
Hashes for ruff-0.0.29-py3-none-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fd79aead1c86e6c77cd6dbc56365fec84b470a789eb98d4ba54ad7f44147eefd |
|
MD5 | 3c018ec544bf0c59cd616dfa7264d89f |
|
BLAKE2b-256 | aedb42fd2c9c9dc9d8580c1aa7f762938b8cc1e2f70faf2bf146ef5616b844c1 |
Hashes for ruff-0.0.29-py3-none-musllinux_1_2_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 923b8a9f2f76a4b46237008e9d88db5219f6d2980766fd3daca2b250e81662c0 |
|
MD5 | f5cc4b888de44d6688938db689f61523 |
|
BLAKE2b-256 | 19569cbf4187e67652e1ae99747d34d41d8553614a17b622536edbb187f9252f |
Hashes for ruff-0.0.29-py3-none-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 139af0a1251bfc80325de67b59e523e8053a74d92c0908634fd1c6849ea0ddad |
|
MD5 | beb2962f13f702fc32692bf79f187db3 |
|
BLAKE2b-256 | d5436bdaff695de417ac910ba8f4279cc1b5915a5c292ef568b8369dd6dab33b |
Hashes for ruff-0.0.29-py3-none-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 37244146c52852ef4c016c49d4701944d68a2e5af6dd7a68a1fe8651c8f870f9 |
|
MD5 | 78863418f224f1ac160ca3fc9ccea387 |
|
BLAKE2b-256 | e64acaa415e1f771bbfef3e699d0e117d52caacc68650c67056d6f59468e2f9d |
Hashes for ruff-0.0.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b7a068515fc287ab84ba41fa16c89888417b068a4c368be0e154cc157d9408a4 |
|
MD5 | 0ee2d78889cc210a2be2f36e7a2d9a7b |
|
BLAKE2b-256 | 05d3347fb87b2ef5272e6adf6c5d4606301bc1df63c1bd2c6804f2e1bf37eba8 |
Hashes for ruff-0.0.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e2d32edead39d692762fa540a8bbc537bed4ee2f43edf646f25eee280880cac4 |
|
MD5 | 8f835da8c4a8e1f3f41d365d0404fc10 |
|
BLAKE2b-256 | 77bcb554b1ac337401ef871e4b488900db4db9f50bcd80fa05665b4a859c7439 |
Hashes for ruff-0.0.29-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4dcd4ce4a3b5a7213139ad17596cb1adc383de8df71d88f6f1af5be33d1ccbdf |
|
MD5 | 0e8d6c4077a4c8adbce5c0846f5c24a5 |
|
BLAKE2b-256 | 2a3a93eda69c9a67105aa88dff0e1a903f58eeb510b42016b2900a91ee9b7a88 |
Hashes for ruff-0.0.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0ae617a9cef14190f2c724e9289d73b24af11cfc7c4c554e331e131211237f8b |
|
MD5 | 738156c617dd2f214c94f41d243e34c4 |
|
BLAKE2b-256 | f2fb2b3fd4c5ac1a4c6ebfe61f88275dd409855987fa4018ef752fe3aeb5a1b7 |
Hashes for ruff-0.0.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fd65a1c92b3995b1bd337728b64f09b94644683d078804720e9aa6f771e1b535 |
|
MD5 | 9b13fcfb4faa6bc224859b12e5a3200f |
|
BLAKE2b-256 | db526c8813f026b720a199f82c68f9bfc683efc326319cc0d8f87c40ea4e2e1e |
Hashes for ruff-0.0.29-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a3a48b27326f6df08ebbfedd043631f8cad952b767da3cab90874818d6b55896 |
|
MD5 | 7ec5ef6938d0ad3563acec0838c3cb56 |
|
BLAKE2b-256 | d798f098f9f620bd62f1bd044e87a86132aaa83fb390cf5fc1ae41954b84709b |
Hashes for ruff-0.0.29-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d73d8a3c1203e66278a6501db1bbfb08572c8e54f9a6c476f5b024afddcc790a |
|
MD5 | 47d63734c3547777b61f017b58f4c8a0 |
|
BLAKE2b-256 | 0c83ca0ff53e4d989df2c59faefa81bfc6c59a7965554e2a6f186e7abd5c0a43 |
Hashes for ruff-0.0.29-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 273921105e6e1e9bfa090341d02903c291bf6b82e48c7abfa751d679f4700da2 |
|
MD5 | f10e09275c281271ba9f8687a4197583 |
|
BLAKE2b-256 | 7cb8b3769ffa6f25ae3813e22896347f1464daf63c5de779f1b73f911bbc161c |
Hashes for ruff-0.0.29-py3-none-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 14c07ecc8986c51b7e7b4a8cd26a89e601856ca2674f5b90ac538b87c576e9bb |
|
MD5 | 432032717ea73ba875db93b071a08231 |
|
BLAKE2b-256 | 9afd3e6079e632c69827bc400a23bc8089e5c64b28fbc6bbd2b14a6ed6d7a649 |