Linting with Ruff

The Problem

When you write code, you may not always follow the best practices. That’s ok! First you should write code that works. Then, before committing, you should clean it up, take a second look, and make sure it’s readable and maintainable.

Examples of common issues:

  • You may have a variable named l or df which is not very descriptive.
  • You use print statements for debugging and forget to remove them.
  • You have unused imports or variables.
  • You have commented out code that should be removed.
  • You have hardcoded passwords or API keys.
  • You have f-string with missing variables.
  • You use eval function or assert statement.
  • Your class name is not in CamelCase, or your function name is not in snake_case.

And many more.

The Solution

As you can see above, all of those issues are easy to catch and fix one-by-one, but there are so many of them that it’s easy to miss some. That’s why we have linters, and ruff seems to be the best one in the Python world.

While formatting tools like ruff format take care of the style, linter like ruff check check for more complex issues and potential bugs. It operates based on set of rules (like the ones above), so you can customize it to your needs. In tapyr we propose a set of rules in ruff.toml that we find useful in Shiny for Python applications.

At first, you may find it annoying that it catches so many issues, but over time you’ll appreciate it. You may wonder what’s wrong with my name l for a list or df for a DataFrame?, but the truth is that it’s better to have meaningful names. Often linter will catch issues that you wouldn’t notice otherwise.

Example: When you write

def divide(a, b):
    c = a / b
    return a

ruff will catch the following issue

example.py:2:5: F841 Local variable `c` is assigned to but never used
Found 1 error.

Only now you realize that you wanted to return c instead of a 😱.

Then there are other rules, for example bandit security checks that looks for hardcoded passwords, insecure use of pickle, SQL queries (potentially leading to SQL injection), eval functions and many more.

Using and configuring ruff

As with formatter, for ruff configuration, please refer to the official linter documentation.

To run ruff on your project, just run:

ruff check

If you want to fix some issues automatically, you can run:

ruff check --fix

Why not use flake8/pylint/pyflakes?

ruff is MUCH faster, while offering (almost) the same functionality. Almost all the rules from flake8/pylint/pyflakes are available in ruff already. On top of that, ruff also offers bandit security checks and more.

Linting the CPython codebase from scratch. (from https://docs.astral.sh/ruff/)