Skip to main content
Version: 2.8 (deprecated)

Linters and formatters

How to activate and use the Python linters and formatters bundled with Pants.


Activating linters and formatters

Linter/formatter support is implemented in separate backends so that they are easy to opt in to individually:

BackendTool
pants.backend.python.lint.banditBandit: security linter
pants.backend.python.lint.blackBlack: code formatter
pants.backend.python.lint.docformatterDocformatter: docstring formatter
pants.backend.python.lint.flake8Flake8: style and bug linter
pants.backend.python.lint.isortisort: import statement formatter
pants.backend.python.lint.pylintPylint: style and bug linter
pants.backend.python.lint.yapfYapf: code formatter
pants.backend.experimental.python.lint.autoflakeAutoflake: remove unused imports
pants.backend.experimental.python.lint.pyupgradePyupgrade: automatically update code to use modern Python idioms like f-strings

To enable, add the appropriate backends in pants.toml:

pants.toml
[GLOBAL]
...
backend_packages = [
'pants.backend.python',
'pants.backend.python.lint.black',
'pants.backend.python.lint.isort',
]

You should now be able to run ./pants lint, and possibly ./pants fmt:

$ ./pants lint src/py/project.py
17:54:32.51 [INFO] Completed: lint - Flake8 succeeded.
17:54:32.70 [INFO] Completed: lint - Black succeeded.
All done! ✨ 🍰 ✨
1 file would be left unchanged.

17:54:33.91 [INFO] Completed: lint - isort succeeded.

✓ Black succeeded.
✓ Flake8 succeeded.
✓ isort succeeded.
How to activate MyPy

MyPy is run with the check goal, rather than lint.

Configuring the tools, e.g. adding plugins

Most of the formatters and linters allow you to configure

OptionWhat it does
versionE.g. flake8==3.8.0.
extra_requirementsAny additional dependencies to install, such as any plugins.
interpreter_constraintsWhat interpreter to run the tool with. (bandit, flake8, and pylint instead determine this based on your code's interpreter constraints.)
argsAny command-line arguments you want to pass to the tool.
configPath to a config file. Useful if the file is in a non-standard location such that it cannot be auto-discovered.
lockfilePath to a custom lockfile if the default does not work, or "<none>" to opt out. See Third-party dependencies.

For example:

pants.toml
[docformatter]
args = ["--wrap-summaries=100", "--wrap-descriptions=100"]

[flake8]
# Load a config file in a non-standard location.
config = "build-support/flake8"
# Change the version and add a custom plugin. Because we do this, we
# use a custom lockfile.
version = "flake8==3.8.0"
extra_requirements.add = ["flake8-2020"]
lockfile = "3rdparty/flake8_lockfile.txt"

Run ./pants help-advanced black, ./pants help-advanced flake8, and so on for more information.

Config files are normally auto-discovered

For tools that autodiscover config files—such as Black, isort, Flake8, and Pylint—Pants will include any relevant config files in the process's sandbox when running the tool.

If your config file is in a non-standard location, you must instead set the --config option, e.g. [isort].config. This will ensure that the config file is included in the process's sandbox and Pants will instruct the tool to load the config.

Skipping a formatter or linter

To temporarily skip a tool, use the --skip option for that tool. For example, run:

$ ./pants --black-skip --flake8-skip lint ::

You can also skip for certain targets with the skip_tool field, which can be useful for incrementally adopting new tools. For example:

project/BUILD
python_sources(
name="lib",
# Skip Black for all non-test files in this folder.
skip_black=True,
overrides={
"strutil.py": {"skip_flake8": True},
("docutil.py", "dirutil.py"): {"skip_isort": True},
},
)

python_tests(
name="tests",
# Skip isort for all the test files in this folder.
skip_isort=True,
)

When you run ./pants fmt and ./pants lint, Pants will ignore any files belonging to skipped targets.

Tip: only run over changed files

With formatters and linters, there is usually no need to rerun on files that have not changed.

Use the option --changed-since to get much better performance, like this:

$ ./pants --changed-since=HEAD fmt

or

$ ./pants --changed-since=main lint

Pants will find which files have changed and only run over those files. See Advanced target selection for more information.

Tips for specific tools

Bandit and Flake8: report files

Flake8 and Bandit can both generate report files saved to disk.

For Pants to properly preserve the reports, instruct both tools to write to the reports/ folder by updating their config files or --flake8-args and --bandit-args. For example, in your pants.toml:

[bandit]
args = ["--output=reports/report.txt"]

[flake8]
args = ["----output-file=reports/report.txt"]

Pants will copy all reports into the folder dist/lint/<linter_name>.

Pylint: how to add first-party plugins

See [pylint].source_plugins for instructions to add plugins written by you.

If you want to write first-party plugins for other linters like Flake8, let us know on Slack.

Bandit: less verbose logging

Bandit output can be extremely verbose, including on successful runs. You may want to use its --quiet option, which will turn off output for successful runs but keep it for failures.

For example, you can set this in your pants.toml:

[bandit]
args = ["--quiet"]

Black and isort can work together

If you use both black and isort, you most likely will need to tell isort to work in a mode compatible with black. It is also a good idea to ensure they use the same line length. This requires tool specific configuration, which could go into pyproject.toml for example:

# pyproject.toml
[tool.isort]
profile = "black"
line_length = 100

[tool.black]
line-length = 100

Pyupgrade: specify which Python version to target

You must tell Pyupgrade which version of Python to target, like this:

# pants.toml
[pyupgrade]
args = ["--py36-plus"]

Autoflake and Pyupgrade are experimental

These tools are marked experimental because we are debating adding a new goal called fix and running them with fix rather than fmt. The tools are safe to use, other than possibly changing how you invoke them in the future.

We invite you to weigh in with what you think!

isort: possible issues with its import classifier algorithm

Some Pants users had to explicitly set default_section = "THIRDPARTY" to get iSort 5 to correctly classify their first-party imports, even though this is the default value.

They report that this config works for them:

# pyproject.toml
[tool.isort]
known_first_party = ["my_org"]
default_section = "THIRDPARTY"

You may also want to try downgrading to iSort 4.x by setting version = "isort>=4.6,<5" in the [isort] options scope.