Ruff is a fast Python linter and code formatter written in Rust that has rapidly gained popularity in the Python ecosystem.
It includes all the standard features expected in any linting framework, such as style checking, error detection, and automatic code fixing capabilities.
This article will guide you through setting up and configuring Ruff for your Python project. You'll leverage the framework's many features and customize them to achieve an optimal configuration for your specific use case.
Let's get started!
Prerequisites
Before proceeding with the rest of this article, ensure you have a recent version of Python (3.12+) and pip installed locally on your machine. This article assumes you are familiar with basic Python development practices.
Getting started with Ruff
To get the most out of this tutorial, you will set up a new Python project to try out the concepts we will discuss.
Start by creating a new directory and setting up a virtual environment:
Activate the virtual environment:
Now, install Ruff using pip:
Let's create a simple Python file with some intentional issues that we can fix with Ruff. Create a file named app.py and add the following problematic code:
This code has several issues that Ruff can detect, including:
- Unused imports
- Inconsistent spacing
- Unused functions
- Missing type hints
Run Ruff to see what it finds:
You should see output similar to the following:
Ruff has identified several issues in your code and even indicates they can be automatically fixed using the --fix option. Try fixing them:
Running this command triggers Ruff to fix all issues, and it can resolve them automatically. Examining the modified file reveals the following changes:
After running Ruff, the improvements are immediately apparent. Ruff removed unused imports, fixed spacing around operators and commas, and properly formatted function parameters for better readability.
The only issue it didn't address was the unused function, as it cannot determine whether it is intentional.
Configuring Ruff
With the initial issues in app.py already fixed, you might wonder if there's anything left to configure. While Ruff works well out of the box, fine-tuning its settings ensures it aligns with your project's specific style and conventions.
Setting up a configuration file allows you to gain more control over which rules Ruff enforces and how it formats your code. Ruff supports configuration through a pyproject.toml file, a common convention in modern Python projects.
Create a pyproject.toml file in your project's root directory:
Open the pyproject.toml file in your text editor and start with a basic configuration:
This configuration enables:
E: Style errors from pycodestyleF: Logic and syntax errors from pyflakes
Now, run Ruff again on your already-fixed code:
No issues should be reported, as you've already fixed the basic style and logic errors.
Let's see what happens when you try to check for import sorting issues.
First, modify app.py to introduce imports that are used but unsorted:
Run Ruff with your current configuration:
Notice that Ruff doesn't report any issues with the imports, even though they're unsorted. That's because you haven't enabled the import sorting rules yet.
Update your configuration to include them:
Now run Ruff again:
One of the first issues Ruff will flag is related to unsorted imports:
Now Ruff reports a new issue: I001, indicating unsorted imports. Let's fix these issues automatically:
After running this command, your app.py should look like this:
Notice how Ruff has properly sorted the imports:
- Standard library imports first (
datetime,json,math) - Third-party imports would come next (none in this example)
- First-party imports and relative imports last (
collections,pathlib) - All imports are alphabetically sorted within their groups
- Import statements (
import x) come before from-imports (from x import y)
This demonstrates the power of Ruff's import sorting capabilities, which can maintain a consistent import style across your entire codebase.
Adding Bugbear rules for better error detection
Let's enhance your configuration to catch more subtle bugs and design issues. The "B" ruleset from Bugbear helps identify common pitfalls that other linters might miss, such as mutable default arguments, unused loop variables, and redundant comparisons.
Now, modify your app.py to include issues that Bugbear rules will detect:
Run Ruff to see what issues it detects:
The first issue, B006, warns against mutable default arguments. Python initializes them once at function definition, causing all calls to share the same instance, which can lead to unexpected behavior.
The second issue, B007, flags an unused loop variable in the nested comprehension. The second loop variable y isn't used, suggesting a potential bug or misunderstanding of nested loops.
Now fix the issues with better coding practices:
Enforcing type annotations
Type annotations improve code readability and help catch type-related bugs early. Adding the "ANN" ruleset to your configuration ensures your code is properly annotated with types:
With this configuration, Ruff will check for missing parameter types, return types, and other annotation issues. Let's run it on our app.py:
Type annotations serve as documentation, making understanding what a function expects and returns easier. They also enable better IDE support and allow tools like MyPy to perform static type checking. Let's add annotations to some of our functions:
Now, each function clearly defines expected input and return types.
Run Ruff again to make sure you've addressed all the type annotation issues:
Ruff should no longer complain about missing type annotations:
Customizing line length and excluding files
As your project grows, you may want to customize Ruff's behavior to match your team's preferences. Setting a custom line length can be necessary if you prefer slightly longer lines than the default, and excluding directories prevents Ruff from wasting time checking files that shouldn't be linted.
This configuration maintains the recommended default line length but you can change it if needed.
It also excludes common directories like virtual environments and cache folders, and sets Python 3.12 as the target version.
Ruff will only suggest features and fixes that are compatible with Python 3.12, ensuring your code remains compatible with your runtime environment.
Per-file rule ignores
Different parts of your codebase may require different linting rules. For example, __init__.py files often contain intentionally unused imports used for re-exporting symbols, and test files may not need type annotations.
You can configure these exceptions using per-file ignores:
This approach allows you to maintain strict standards across your main codebase while accommodating special cases without compromising overall code quality.
Using Ruff as a code formatter
Beyond linting, Ruff can also format your code automatically. Ruff's formatter is designed to be compatible with Black, the popular Python formatter, while offering significantly better performance.
Using Ruff for both linting and formatting helps simplify your toolchain and ensures consistent code style across your project
The formatter is invoked using the format command:
Create a new file with a command-line editor of your choice to avoid auto-indentation for this example. Add the following code with some formatting issues to see how Ruff's formatter works:
The code has multiple formatting issues, including inconsistent spacing around commas, operators, and colons, improper dictionary formatting with uneven spacing, missing blank lines between definitions, and single quotes instead of double quotes for strings.
Now, run Ruff's formatter on this file:
After formatting, your messy.py file should look much cleaner:
Ruff has automatically fixed all these issues:
- Added consistent spacing after commas in function parameters
- Added spacing around operators in the return expression
- Added a blank line between the function and variable assignment
- Standardized the dictionary formatting with no spaces after { or before }
- Converted single quotes to double quotes for strings
- Added consistent spacing around colons in key-value pairs
These formatting improvements make your code more readable and consistent, following Python best practices like PEP 8.
One of Ruff's strongest features is its ability to both lint and format code. Instead of using multiple tools (like Flake8 for linting and Black for formatting), you can simplify your toolchain with just Ruff:
Combining these commands in your workflow provides the benefits of comprehensive linting and consistent formatting within a single tool.
Integrating Ruff with pre-commit hooks
After configuring Ruff for linting and formatting, the next step is to automate these checks in your development workflow.
Pre-commit hooks provide an excellent way to ensure code quality standards are met before each commit.
Since your project directory isn't currently a Git repository, you'll need to initialize one first:
Next, create a .gitignore file to exclude unnecessary files from version control:
With your Git repository initialized, you can now set up pre-commit hooks:
Create a pre-commit configuration file named .pre-commit-config.yaml to integrate Ruff for both linting and formatting:
This configuration defines two hooks:
ruff: Runs the linter with automatic fixes enabledruff-format: Applies formatting to your code
Install the hooks into your Git repository:
You should see output confirming the installation:
First, remove all the code in app.py to verify that your setup works correctly. Then, introduce some rule violations to test your configuration:
Now stage your changes and try to commit them:
The pre-commit hooks should run automatically and fix the issues:
While Ruff fixed some issues (like spacing and formatting), it still reports errors related to missing type annotations, which require manual fixes.
To fix those issues, add the necessary type annotations:
Stage your changes again:
This time, the commit should succeed.
In some situations, you might need to bypass the pre-commit hooks temporarily:
However, this should be used sparingly and only in exceptional circumstances.
Final thoughts
This guide covered setting up Ruff, automating fixes, enforcing coding standards, and improving development workflows. For more advanced configurations and features, visit the official Ruff documentation.
Thanks for reading!