Motivation
Code quality tools are essential for maintaining Python projects, but managing multiple tools creates unnecessary complexity in development workflows. Each tool requires separate installation, configuration, and execution.
Installing and maintaining multiple tools creates overhead:
# requirements-dev.txt
flake8==6.1.0
black==23.7.0
isort==5.12.0
pycodestyle==2.12.1
This setup requires remembering multiple commands:
isort .
black .
flake8 .
pycodestyle .
Introduction to Ruff
Ruff is a blazing-fast Python linter written in Rust that combines multiple code quality tools into a single package. Install it using pip:
pip install ruff
Unified Code Quality Checks
Let’s see how Ruff handles various code quality issues. First, configure Ruff in pyproject.toml:
[tool.ruff]
line-length = 88
# Exclude files and directories
exclude = [
".git",
".venv",
"__pycache__",
]
[tool.ruff.lint]
select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
]
The selected rules include:
"E"
: Style and PEP 8 compliance (from pycodestyle)"F"
: Python logical errors (from pyflakes)"I"
: Import sorting and organization (from isort)
Let’s check a Python file with various issues:
# my_code.py
import pandas as pd
import numpy as np
def calculate_something(x, y):
result = undefined_variable + np.array([1, 2])
unused = "This variable is never used"
return result+y
very_long_string = "This is an extremely long string that will definitely exceed the 88 character limit that was specified in your pyproject.toml file"
Running Ruff identifies several issues:
ruff check my_code.py
Output:
my_code.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import pandas as pd
2 | | import numpy as np
| |__________________^ I001
3 |
4 | def calculate_something(x, y):
|
= help: Organize imports
my_code.py:1:18: F401 [*] `pandas` imported but unused
|
1 | import pandas as pd
| ^^ F401
2 | import numpy as np
|
= help: Remove unused import: `pandas`
my_code.py:5:14: F821 Undefined name `undefined_variable`
|
4 | def calculate_something(x, y):
5 | result = undefined_variable + np.array([1, 2])
| ^^^^^^^^^^^^^^^^^^ F821
6 |
7 | unused = "This variable is never used"
|
my_code.py:7:5: F841 Local variable `unused` is assigned to but never used
|
5 | result = undefined_variable + np.array([1, 2])
6 |
7 | unused = "This variable is never used"
| ^^^^^^ F841
8 | return result+y
|
= help: Remove assignment to unused variable `unused`
my_code.py:10:89: E501 Line too long (151 > 88)
|
8 | …
9 | …
10 | …t will definitely exceed the 88 character limit that was specified in your pyproject.toml file"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E501
|
Found 5 errors.
[*] 2 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
After running Ruff with automatic fixes:
ruff check --fix my_code.py
Output:
Found 5 errors (2 fixed, 3 remaining).
Some issues are automatically fixed, while others require manual intervention:
import numpy as np
import pandas as pd # This is removed automatically
def calculate_something(x, y):
result = undefined_variable + np.array([1, 2]) # Needs to be fixed manually
unused = "This variable is never used" # Needs to be fixed manually
return result + y
# Needs to be fixed manually
very_long_string = "This is an extremely long string that will definitely exceed the 88 character limit that was specified in your pyproject.toml file"
The final, clean code after manually editing:
import numpy as np
def calculate_something(x, y):
result = np.array([1, 2])
return result + y
very_long_string = (
"This is an extremely long string that will definitely exceed the "
"88 character limit that was specified in your pyproject.toml file"
)
Conclusion
Ruff simplifies Python code quality management by replacing multiple tools with a single, faster solution. Its unified approach not only reduces configuration complexity but also provides clear guidance on which issues can be fixed automatically and which require manual intervention.