Linting
As codebases become larger, it is easier for mistakes to creep in. Linters are tools that check the code for signs of potential problems, like unused variables or uncommon usage patterns.
There are numerous linters for Python, and many of them can be configured
through pyproject.toml
.
For this tutorial, we will use Ruff, a relatively recent suite of linters
and formatters tools, which draws inspiration from previous tools.
Install the linter
Like many Python analysis tools, Ruff is available as a package we an install.
The linter is an optional dependency: we need it while developing, but it's not something we want our users to install. We'll therefore put it in a separate category of development-only requirements.
Add the following to your pyproject.toml
:
[project.optional-dependencies]
dev = [
"ruff",
]
Install the new dependency
Run python -m pip install .[dev]
This will install your core package along with the dev
"extra".
You should see that pip
is searching for ruff
and its dependencies.
Run the linter and address issues
Now that it's installed, we can use the linter.
Run the linter
Run python -m ruff check src
from the root of your project.
Look at the error reported about an unused variable.
The error is correctly identified, but the solution suggested is
perhaps not the best... Address the error however you think is best,
and rerun ruff
to check that it reports no problems.
Configure linting
ruff
uses a default set of rules when checking your code, but it can be
customized to add or remove from that rulset. This is useful when you want
to be stricter or, conversely, you want to ignore a particular rule.
Add the following section in pyproject.toml
:
[tool.ruff.lint]
select = [
"E",
"F",
"B",
"SIM",
"I",
"ARG"
]
The above configuration specifies the set of rules to use.
Try the new configuration
Rerun the linter. You should now seem some additional errors.
Each error is accompanied by a code like UP015
, indicating the rule
it violates. The rules are grouped in categories starting with the
same letter. For example, I
indicates rules related to the ordering of
imports, while ARG
rules have to do with function arguments.
With the above configuration, we have asked Ruff to check the categories
E
, F
and so on.
In our case, the additional category detected a bug: we are not using
one of the function's arguments. Assuming this is unintentional,
change the function body to fix the bug (use the y
argument however
you want).
Rerun ruff
to check that the errors are gone.
Exclude files from linting
Ruff will try to detect all relevant files to check, but sometimes
you may want to exclude some files from it. For example, when you
installed your package, pip
may have created a build
directory
with a copy of your code. Running Ruff on those files will generally
report the same errors as in src
, which is redundant.
We have been working around this by restricting the folder that ruff
looks at through passing the extra command-line argument src
,
but it can be easier to configure this in pyproject.toml
.
Add the following entry in the file, below the previous configuration:
[tool.ruff.lint]
...
exclude = ["build/**"]
Now you can simply run python -m ruff check
, and the build
diretory
will be ignored. This also gives you finer-grained control, since
you can exclude individual files.
Further resources
- The Ruff documentation describes the different rules you can select from.
- It also explains how to ignore a rule, either entirely or for individual lines or files.
- Flake8 is another popular linter which can be customized similarly.
- Ruff can also be used for automatic formatting of your Python code, enforcing rules like line length and naming conventions. Other formatters include black and pycodestyle.