pwshub.com

Linting with Ruff: the Python linter built with Rust

Did you know that the efficiency of your linter can significantly affect your productivity?

After adding a small feature to a big project I was working on recently, I needed to use the configured linter to align my addition with the style of the rest of the code.

So I ran the linter.
30 seconds in…no progress bar.
Me: “It’s been a while. Let me get something to drink and be back.”

Two minutes in.
Me: “It’s not done yet? It’s been quite a long time, but it should finish up soon.”

Five minutes in.
Me: “It’s still running? Is that why there’s no progress bar? I probably didn’t look at how to add the progress bar, but I’m already in too deep to start all over again with the progress bar.”

Nine minutes in.
Me, holding my drink, staring into the abyss: “…did I break it?”
Linter: Completes
Me: “So NOW you’re done!?”

From the look of things, a faster linter would have been less frustrating. Imagine having to run the linter multiple times. I, like many others, have done so, and we’ve lost our flow and gotten distracted in the process.

That’s why I’m particularly excited to help you enjoy your next beverage in peace by writing this article about a tool called Ruff, a linter and code formatter that should help us avoid this problem going forward.

We’ll start with what Ruff is and how it solves inefficiencies. We’ll then look at how to set it up and use it. Finally, we’ll do a formal comparison of Ruff with similar tools to understand just how well it performs.

So what is Ruff?

Before jumping into Ruff, let’s explain what linters and code formatters are. If you’re already familiar with this, then go ahead and skip to the setup section.

Linters and code formatters are tools that tend to get slower as your projects get bigger. A linter scans a codebase to check for potential bugs and errors, enforces consistent styling throughout your codebase, and improves code quality. A code formatter scans your codebase and restructures components to fit any style you describe and enforce consistency throughout your codebase.

Ruff is a Python linter and code formatter. What sets it apart from other linters and code formatters is that it’s built in Rust, meaning that it performs well and is more efficient. Ruff also lints and formats code in large projects very quickly, so quick that I had to double check that Ruff was performing any linting or formatting during my benchmarks. (I didn’t even have enough time to finish my drink!).

To add to the list of Ruff’s features, it supports over 800 lint rules. Many of the rules are inspired by other linters like Flake8, Pylint, isort, pyupgrade, and many more, making it easier to use Ruff in place of these linters without extensively translating your linter’s configuration to work with Ruff.

Setting up Ruff

Now that we’ve done a little look into Ruff, it’s time to learn how to set up and use it in your project.

Installing Ruff

There are multiple methods that to install Ruff. You’re fine picking the method that suits your needs, preferences, or taste.

Installing with pip:

pip install ruff

If you prefer using your OS package manager, you can for example use these:

# macOS
brew install ruff
# Arch Linux
pacman -S ruff
# Alpine linux
apk add ruff

You can also install Ruff through conda:

conda install -c conda-forge ruff

Or manually, using standalone installers:

# On macOS and Linux.
curl -LsSf https://astral.sh/ruff/install.sh | sh
# On Windows.
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.5.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.5.0/install.ps1 | iex"

Note: Versions 0.5.0 upwards are the only versions of Ruff available through the standalone installer.

Configuring Ruff

After installing Ruff, you’d have to configure it if you want to lint or format your code in a specific way. Ruff allows you to configure its linting and formatting through the pyproject.toml or ruff.toml files.

pyproject.toml:

# pyproject.toml
[tool.ruff]
line-length = 88
exclude = ["build/", "docs/"]
[tool.ruff.lint]
select = ["E", "F", "UP", "B", "SIM", "I"]

ruff.toml:

line-length = 88
exclude = ["build/", "docs/"]
[lint]
select = ["E", "F", "UP", "B", "SIM", "I"]

These configurations set the maximum line length to 88 characters, exclude the build/ and docs/ directories from linting and formatting, and enable the pycodestyle (E), Pyflakes (F), pyupgrade (UP), flake8-bugbear (B), flake8-simplify (SIM), and isort (I) lint rules.

Using Ruff

Now, let’s look into using Ruff as a linter and as a code formatter:

Using Ruff as a linter

ruff check is the primary way to lint Python files in the current directory (excluding specified files in your configuration). Running ruff check in the terminal lints all Python files in the current directory.

Sometimes, ruff check finds automatically fixable errors in your codebase. If you want ruff check to fix all the fixable errors it finds, run the command with the --fix flag:

ruff check --fix

To lint all the files in the current directory and re-lint any that changes, add the --watch flag:

ruff check --watch

To lint a specific file, add the path as an argument to ruff check:

ruff check path/to/specific/file.py

Using Ruff as a code formatter

Running ruff format in the terminal formats all Python files in the current directory.

To format a specific file, add its path as an argument to ruff format:

ruff format path/to/file.py

You can also pass a path directory as an argument to ruff format if you wish to format all the files in that directory:

ruff format path/to/directory/

Now you know how to use Ruff as both a linter and formatter, and you’re all set to use Ruff in your project.

Comparing Ruff with existing linters and code formatters

In this section, we’ll compare Ruff to Flake8, Black, and Pylint in terms of performance, configuration process, community, and support.


More great articles from LogRocket:

  • Don't miss a moment with The Replay, a curated newsletter from LogRocket
  • Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
  • Use React's useEffect to optimize your application's performance
  • Switch between multiple versions of Node
  • Discover how to use the React children prop with TypeScript
  • Explore creating a custom mouse cursor with CSS
  • Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Performance

I ran a simple benchmark to compare these tools, and here are the results:

# pygame snake game (small code base)
Ruff (linting) took 1.0 seconds
Flake8 (linting) took 2.0 seconds
Pylint (linting) took 1.0 seconds
Ruff (formatting) took 0 seconds
Black (formatting) took 9.0 seconds
# cpython (large code base)
Ruff (linting) took 1.0 seconds
Flake8 (linting) took 19.0 seconds
Pylint (linting) took 2.0 seconds
Ruff (formatting) took 0 seconds
Black (formatting) took 443.0 seconds

I’ve created a GitHub repository for the benchmarking scripts and codebases if you wish to run it yourself or take a look at how I made the benchmark.

From the results, you can see that Ruff performs very well compared to the other linters and code formatters in the benchmark. In the linting category, Ruff is followed by Pylint, which falls behind as the project gets bigger, and then Flake8, which falls further behind as the project gets bigger.

Black is the only other formatter in this benchmark. This benchmark shows that Ruff is the better-performing formatter. Ruff takes less than one second to format the codebase regardless of the size, compared to Black, which took nine seconds on the smaller code base and took almost 50x the time in a codebase as large as CPython.

However, you should note that the benchmark doesn’t account for how different each tool operates by default. This may affect how much work each tool does during the benchmark. Nevertheless, it’s a good starting point to see how well these tools scale up in bigger projects.

Configuration

Right out of the box, Ruff, Pylint, Black, and Flake8 have default settings that allow you to use them without configuration. However, you may want to configure the tools to operate based on your tastes.

I’ve written simple and identical configurations for these tools. Here’s how they look.

Ruff (ruff.toml):

line-length = 88
exclude = ["__pycache__", "build", "dist", ".git"]
[lint]
select = ["E", "F", "W"]

Flake8 (.flake8):

[flake8]
max-line-length = 88
exclude = .git,__pycache__,build,dist

Pylint (.pylintrc):

[MASTER]
ignore=__pycache__,build,dist,.git
max-line-length=88

These configurations set the maximum allowed line to a length of 88 and exclude the .git, __pycache__, build, and dist directories from being linted. In Ruff’s configuration file, the select option specifies which linting rules or categories of rules to enable: E and W (for error and warning) from Pylint, and F from Pyflakes.

Ruff lets you configure its linting and formatting rules in the ruff.toml file. You can also configure Ruff within the project’s general configuration file: pyproject.toml.

Flake8 allows you to specify options for linting as part of the command line arguments to the flake8 command.

If you don’t want to specify the options every time you’re calling the flake8 command, you can configure those options via files like .flake8, setup.cfg, and tox.ini (as we saw in the list above). Let’s look at a command-line arguments version of the configuration from the list earlier:

flake8 --max-line-length 88 --exclude .git,__pycache__,build,dist

Writing options as arguments is helpful if you need to specify configurations you want to run once or a few times without editing the configuration file.

Flake8 also allows you to use and configure plugins, which while allowing you to get more specific functionality, can add complexity to the overall configuration.

Pylint has a more complex configuration due to its extensive rule set. It is well-supported compared to Ruff but requires a much more complex setup process. As of writing this, I struggle with getting Pylint working with virtual environments.

Now, let’s look at simple configuration for formatting in Ruff and Black.

Black (pyproject.toml):

[tool.black]
line-length = 88
exclude = '/(\.git|\.venv|_build|build|dist|__pycache__)/'

Ruff (pyproject.toml):

[tool.ruff]
line-length = 88
exclude = ["__pycache__", "build", "dist", ".git", ".venv", "_build"]

These configurations ensure that all lines in the Python scripts to be formatted must be at most 88 lines and exclude the .git, __pycache__, build, _build, dist, and venv files and folders from being formatted.

Ruff and Black work out of the box with good defaults, formatting the codebase in similar styles. Unlike Ruff, Black focuses on formatting, meaning you need to combine it with a linter to get linting benefits. Like Ruff, you can configure Black using the pyproject.toml file.

Community and support

Ruff, Pylint, Black, and Flake8 had their initial releases in 2022, 2001, 2019, and 2014 respectively. Ruff being newer in comparison means that it has had less time to grow compared to Flake8, Black, and Pylint. This also means that you may not see as much support to help you achieve specific goals in Ruff compared to these tools.

However, Ruff’s community is growing rapidly and has a high adoption rate. Even major projects like Apache Airflow and FastAPI use Ruff in managing their projects, meaning that Ruff has a promising future.

Conclusion

Ruff being able to format and lint a codebase also means that you don’t need an extra setup to get these benefits of a linter and code formatter together. With Ruff’s configuration system, setting up linting and code formatting for your projects is easy, and being built for performance means you can use Ruff in larger projects.

Ruff’s growing community and adoption rate make it a good choice for linting and formatting because you don’t have to worry about losing support in the future. Taking its growth rate into account its support in the future looks promising.

Source: blog.logrocket.com

Related stories
1 month ago - JSON is one of the most popular data structures in and out of web development and data handling because it’s […] The post Visualize JSON data with these popular tools appeared first on LogRocket Blog.
1 month ago - ESLint is well adopted and covers virtually all relevant development use cases with its rich feature set. Learn more in this guide. The post ESLint adoption guide: Overview, examples, and alternatives appeared first on LogRocket Blog.
2 days ago - A linter is a tool that scans code for potential issues. This is invaluable with a programming language like JavaScript which is so loosely typed. Even for TypeScript, which is a strongly typed language whose compiler does a great job of...
1 week ago - Deno's features and built-in TypeScript support make it appealing for developers seeking a secure and streamlined development experience. The post Deno adoption guide: Overview, examples, and alternatives appeared first on LogRocket Blog.
1 month ago - Vitest is a powerful testing library built on top of Vite that is growing in popularity. You can use Vitest for a range of testing needs. The post Vitest adoption guide: Overview, examples, and alternatives appeared first on LogRocket Blog.
Other stories
1 hour ago - This release candidate, a near-final look at Deno 2, includes the addition of Node's process global, better dependency management, and various API stabilizations, and more.
1 hour ago - Published: September 19, 2024 The CSS Working Group has combined the two CSS masonry proposals into one draft specification. The group hopes that...
1 hour ago - Stay organized with collections Save and categorize content based on your preferences. Published: September...
3 hours ago - DNS monitoring tool is a cloud-based scanner that constantly monitors DNS records and servers for anomalies. This tool aims to ensure that users are sent to genuine and intended website pages, instead of fakes or replicas. It alerts users...
4 hours ago - Email spoofing is a malicious tactic in which cybercriminals send fake emails that look like they come from trusted organizations or individuals. When unsuspecting users act on these emails, they may unknowingly share sensitive data,...