iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐍

Notes on Python Formatters and Linters

に公開
2022/12/17

Overview

This is a memorandum regarding my Python development environment, primarily focusing on formatters and linters.
While there are many similar articles and this might be a well-trodden path, I'm leaving this note for my future self because if I don't summarize it, I feel like I'll end up searching for tools and configuration methods every single time.

Formatter

The reason I introduced a formatter is that I don't want to stress over coding styles.
I have a meticulous side that makes me want to fix minor details.
Despite that, my preferred style changes every few months, and I sometimes feel like going back and fixing old source code based on my mood at the time, leading to a mix of styles.
Additionally, when adding features after some time has passed, I might forget the original style and write in a different one.
Furthermore, it is useful for unifying coding styles when developing in a team.
For these reasons, I introduced formatters.

The formatters I introduced are Black and isort.

black

For the reasons mentioned above, I introduced Black, which has few customizable items and strong constraints.
To be honest, there are some rules I don't like, but most of them are for subjective and moody reasons.
Also, since it's futile to argue over preferred styles during team development, I decided to stick to Black's defaults.

You can install it with the following command:

pip install black

Also, the following command formats the specified file or files within a directory:

black {source_file_or_directory}

According to the official documentation, if the --exclude option is not set, files specified in .gitignore will not be formatted.
Additionally, if there are files you want to exclude from formatting in addition to those specified in .gitignore, you can specify them with the --extend-exclude option.
Configuration details, including exclusion settings, can be specified when running from the command line, but they can also be written in pyproject.toml.

pyproject.toml
[tool.black]
# Official documentation
# https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-format

# Comment out if you want to change the line length from the default
# line-length = 88

# Files or directories to exclude from formatting that you want to add to the defaults
extend-exclude = '''
(
      \.git
    | migrations/  # Automatically generated DB migration files
)
'''

isort

isort is a formatter that sorts imports alphabetically.
Since Black does not sort library imports, I introduced isort.

You can install it with the following command:

pip install isort

Also, the following command formats the specified file or files within a directory:

isort {source_file_or_directory}

Configuration is written in pyproject.toml.
When using it alongside Black, write the profile setting in the configuration file pyproject.toml.

pyproject.toml
[tool.isort]
# Configuration for coexistence with Black
# https://pycqa.github.io/isort/docs/configuration/black_compatibility.html
profile = "black"

# Exclude files specified in the .gitignore file
skip_gitignore = true

# Files or directories to exclude from formatting that you want to add to the defaults
extend_skip_glob = ["**/migrations/*"]  # Automatically generated DB migration files

Linters

I introduced linters to detect problems that cannot be fixed by formatters.
I chose flake8 and mypy as the linters.

flake8

You can install flake8 with the following command:

pip install flake8

Also, the following command checks the specified file or files within a directory:

flake8 {source_file_or_directory}

Configuration is written in setup.cfg.

setup.cfg
[flake8]
# https://flake8.pycqa.org/en/latest/user/configuration.html

# Match the maximum line length with Black
max-line-length = 88

# Files or directories to exclude from formatting that you want to add to the defaults
# https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-exclude
# flake8 does not have an option to bulk exclude files specified in the .gitignore file
# Defaults are .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.nox,.eggs,*.egg
extend-exclude = **/migrations/*,venv/,.venv/

# Configuration for rules to ignore
# https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-ignore
# Settings to avoid conflicts with Black
# Line too long (E501)
# Line break occurred before a binary operator (W503)
ignore = E501,W503

mypy

By coding while using type hints, methods can be auto-completed and the intent of the code can be conveyed, so I make a conscious effort to use them.
I introduced mypy as a tool to check for places where type hints are not used or where operations do not match the types.
If you are developing with VSCode, many people might use the type-checking feature of Pylance, which is developed by Microsoft. However, considering integration with other tools like Git hooks, CI, or mypy plugins for frameworks like Django, mypy—developed by the Python official team—seems like a good choice.

mypy can be installed with the following command:

pip install mypy

Also, the following command checks the specified file or files within a directory:

mypy {source_file_or_directory}

Configuration is written in pyproject.toml.

pyproject.toml
[tool.mypy]
# https://mypy.readthedocs.io/en/stable/config_file.html#example-pyproject-toml

# Enable strict mode
strict = true

# Countermeasure for the following error
# module is installed, but missing library stubs or py.typed marker
# Set ignore_missing_imports to true for modules without stubs
# [[tool.mypy.overrides]]
# module = [
#     "module1",
#     "module2"
# ]
# ignore_missing_imports = true

# Plugin settings
# plugins = ["mypy_django_plugin.main"]

# Configuration for the Django mypy plugin
# [tool.django-stubs]
# https://github.com/typeddjango/django-stubs
# django_settings_module = "mysite.settings"

External Tool Integration

Visual Studio Code

Add the following to your VS Code user or workspace settings.
From what I've observed, it seems to read the settings from the CLI configuration files.

settings.json
{
    "[python]": {
        // Format with Black on save, but Black's exclude settings are ignored
        // https://stackoverflow.com/questions/70199494/how-to-make-vscode-honor-black-excluded-files-in-pyproject-toml-configuration-wh
        "editor.formatOnSave": true,
        // Format with isort on save
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        }
    },
    "python.analysis.typeCheckingMode": "off",  // Disable VS Code's built-in type checking since we're using mypy
    "python.formatting.provider": "black",
    "python.linting.flake8Enabled": true,
    "python.linting.mypyEnabled": true,
}

Git

By using Git hooks, you can prevent code from being committed or pushed in an improper state.
I referred to this documentation.

pre-commit

Formatters, linters, and type checking should be performed before committing, so that a commit only succeeds if these pass.
Create a file named .git/hooks/pre-commit and give it execution permissions.
Add the following content to the file:

.git/hooks/pre-commit
#!/bin/bash -eu

# Git hooks are executed in the repository's root directory
# https://git-scm.com/docs/githooks

# Load the virtual environment
source ./venv/bin/activate

# Run formatters, linters, and type checking
isort .
black .
flake8 .
mypy .

pre-push

Since I often separate commits for test code and production code, I choose to skip testing before each commit and instead run tests before pushing. The push will only proceed if the tests succeed.

.git/hooks/pre-push
#!/bin/bash -eu

# Git hooks are executed in the repository's root directory
# https://git-scm.com/docs/githooks

# Load the virtual environment
source ./venv/bin/activate

# Run formatters, linters, and type checking
isort .
black .
flake8 .
mypy .

# Run tests
# python manage.py test  # Django tests

Summary

I have documented a memorandum on setting up a Python development environment by introducing Black and isort as formatters, and flake8 and mypy as linters.
I also covered the configuration of these formatters and linters, as well as how to integrate them with external tools like VS Code and Git hooks.
I hope this serves as a helpful reference for my future self and for all of you.

GitHubで編集を提案

Discussion