‹ back home

Notes on ruff

2023-01-20

ruff is a tool to check and lint Python code. It’s written in Rust and it really is blazing fast. The first time I ran it on a real codebase I immediately got an error and, for a split-second, though it was an execution error. It had actually run successully in a few milliseconds and found an issue in my code (a silly one, but a valid one).

A flake8 replacement

ruff implements all the rules from pyflakes and pycodestyle, which essentially implies it serves as a full replacement for flake8. Additionally, it implements a lot of rules for flake8 plugins (these need to be enabled manually).

I’ve transitioned a couple of projects from flake8 to ruff and I’m very happy with the results. On top of re-implementing the rules in a very fast way, ruff also implements “severities”: diagnostic may be Errors, Warnings, Info or Hint. This helps a lot; when drafting code (e.g.: in a very rough state), errors (e.g.: missing import, reading from undefined variable, etc) are not the same as hints (e.g.: missing or extra whitespace, too many empty newlines, etc).

I actually suggested implementing severities for flake8, but this was deemed completely unacceptable; the authors believe that every single user out there should re-implement this mapping themselves, which is an absurd idea that doesn’t scale.

Finally, ruff can auto-fix a lot of issues that existing tools could only detect (for example: rewriting loops using comprehensions).

An isort replacement

There’s nothing truly broken with isort to be honest. It’s a great tool. And ruff implements all its functionality into the same codebase. It kinda makes sense; both tools need to parse and entire codebase and analyse it. Adding a new check to a single tool is simpler implementing two tools. Plus, combining the auto-fixing of flake8 rules with auto-fixing of imports into one action makes code maintenance a lot simpler.

A pyupgrade replacement

You can see where this is going, right? ruff also implement fixes done by pyupgrade.

For those not familiar, pyupgrade allows specifying which version of python a project is targeting, and will automatically update syntax for newer language and drop obsolete syntax.

This usually removes a lot of redundant code, and updates code to newer (easier to read) syntax.

Aside from fixing these automatically, ruff will show these potential fixes as warnings when checking code, or when running via a LSP…

A dedicated language server: ruff-lsp

ruff-lsp is a language server built on top of ruff. It’s very simple and straightforward to configure in neovim or any of many other code editors. It provides diagnostics for all of ruff’s checks, as well as actions to apply fixes via an IDE’s interface.

My experience so far with ruff-lsp has been very pleasant. A lot of useful feedback when editing any Python file. It’s fast, and has very sensible defaults, so even for projects that don’t have a ruff configuration the diagnostics are quite valuable (e.g.: unused variable, unused import, missing definition, all valid checks).

Summarising

You can read more about ruff in today’s post by the author. If you’re interesting in switching a project from flake8+isort+pyupgrade, it’s usually pretty straighforward. Here’s an example of how I switched todoman to use ruff.

For project with many developers (e.g.: those where you work in a team), be sure to align with other devs. They will need to ensure that their workflow is updated.

— § —