Set up Ruff for formatting and checking your code
This tutorial helps you set up Ruff to automatically format your Python code and check it for common errors and style issues.
Prerequisites
Before starting, make sure you have uv installed on your system. You can install it following the installation guide.
Creating a sample project
Let’s create a new project to demonstrate Ruff:
$ uv init ruff-demo
Initialized project `ruff-demo` at `/path/to/ruff-demo`
$ cd ruff-demo
If you see error: command not found: uv, finish the installation guide and reopen your shell.
Notice the new main.py file alongside pyproject.toml, .python-version, and a fresh .git/ directory. uv populates a starter main.py so the project is runnable from the first command. Open main.py and replace its contents with the following messy code:
import sys,os
from pathlib import Path
import json
def hello(name:str='World'):
print(f'Hello, {name}!')
unused_var = 42
if __name__=='__main__':
hello()This code has several style issues that Ruff can help fix:
- Unsorted and poorly formatted imports
- Inconsistent spacing
- Unused variables
- Missing whitespace around operators
Installing Ruff
Add Ruff as a development dependency of your project:
$ uv add --dev ruff
Using CPython 3.14.4
Creating virtual environment at: .venv
Resolved 2 packages in 81ms
Installed 1 package in 3ms
+ ruff==0.15.12
Your exact Python and Ruff versions may differ. Notice the new .venv/ directory: uv add created the project’s virtual environment because this is the first dependency. Future uv commands reuse it instead of touching your system Python.
Configuring Ruff
Open the pyproject.toml file in your project and add this Ruff configuration to the bottom:
[tool.ruff.lint]
extend-select = ["B"]By default, Ruff checks for a large number of syntax errors. This configuration extends the defaults with additional checks (B) for Python errors and gotchas.
Running Ruff
Let’s check our code with Ruff:
$ uv run ruff check .
E401 [*] Multiple imports on one line
--> main.py:1:1
|
1 | import sys,os
| ^^^^^^^^^^^^^
2 | from pathlib import Path
3 | import json
|
help: Split imports
F401 [*] `sys` imported but unused
--> main.py:1:8
|
1 | import sys,os
| ^^^
|
help: Remove unused import
... (four more diagnostics: F401 for `os`, `pathlib.Path`, and `json`, plus F841 for `unused_var`)
Found 6 errors.
[*] 5 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
Ruff prints a multi-line annotated diagnostic for each issue, pointing at the offending span and suggesting a fix. Each block ends with a help: line. Notice the [*] marker on five of the six issues: it means Ruff can auto-fix them. The sixth is hidden behind --unsafe-fixes because removing an assignment can change runtime behavior.
Let’s apply the safe fixes:
$ uv run ruff check --fix .
F841 Local variable `unused_var` is assigned to but never used
--> main.py:4:5
|
2 | def hello(name:str='World'):
3 | print(f'Hello, {name}!')
4 | unused_var = 42
| ^^^^^^^^^^
|
help: Remove assignment to unused variable `unused_var`
Found 6 errors (5 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
Open main.py and notice that all four imports are gone. Ruff removed them because none of them were used; the lint and the auto-fix don’t preserve dead imports. The unused_var = 42 line stayed because that fix is gated behind --unsafe-fixes. The file now reads:
def hello(name:str='World'):
print(f'Hello, {name}!')
unused_var = 42
if __name__=='__main__':
hello()Spacing and quotes still look messy because ruff check --fix only applies lint fixes; formatting is a separate step.
Using Ruff’s Formatter
Ruff can also format your code.
Let’s enable formatting:
$ uv run ruff format .
1 file reformatted
Reopen main.py to see the result. Spacing around = and ==, double quotes, and a blank line between the function and the if __name__ block now match standard Python style:
def hello(name: str = "World"):
print(f"Hello, {name}!")
unused_var = 42
if __name__ == "__main__":
hello()Adding Pre-commit Hooks
To automatically run Ruff before each Git commit, create a .pre-commit-config.yaml file:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
hooks:
- id: ruff-check
args: [ --fix ]
- id: ruff-formatInstall pre-commit and the hooks:
$ uvx pre-commit install
Downloading virtualenv (7.2MiB)
Downloaded virtualenv
Installed 10 packages in 9ms
pre-commit installed at .git/hooks/pre-commit
If you see An error has occurred: InvalidConfigError: ... is not a git repository, the project is missing a .git/ directory. uv init creates one automatically, so this only happens if you started outside a project root or deleted .git/. Run git init and try again.
Now Ruff will automatically check and format your code whenever you commit changes!
Next Steps
You’ve successfully set up Ruff for code formatting and linting. To learn more:
- How to configure recommended Ruff defaults for a more comprehensive set of linting rules
- How to sort Python imports with Ruff
- Ruff reference page for a full overview of Ruff’s capabilities
- Explore additional Ruff rules to enable
- Configure Ruff in your editor