Pythonプロジェクトにlinter・formatterを導入してコードの品質を向上させる

# 作るもの
既存のPythonプロジェクト（ここではFastAPI）にlinterとformatterを導入します。
チーム開発をしているので、コードの品質向上とプルリク時にレビュアーの負担を下げるのが目的です。

# やること
* linter・formatterツールをプロジェクトに導入
* コミット前に自動で実行してくれるようにする

# 使うツール
### 1. black
PEP8に準拠したコードフォーマッター。
細かい設定をする必要もないので、導入しやすいです。

https://github.com/psf/black

https://black.readthedocs.io/en/stable/

### 2. flake8
PythoのLinter（静的解析ツール）で、pycodestyle、pyflakes、mccabeをラッパーしています。

https://pypi.org/project/flake8/

### 3. isort
import文のフォーマットをしてくれます。
PEP8の規定に基づいて自動で修正してくれますので、こちらも導入します。

https://pycqa.github.io/isort/

### 4. mypy
コードの型チェックをしてくれます。
ただ、これはプロジェクト次第では別になくても良いかなと思ってます。
そもそもpythonが動的型付け言語で、あまり型について気にせず書けるというが強みでもあると思ってるので。

https://github.com/python/mypy

### 5. pre-commit
こちらはlinter・formatterではなくて、コミット前に自動で実行してチェックしてくれるようにするためのツールです。

https://pre-commit.com/

# プロジェクトに導入
### 1. ツールをインストール
「使うツール」に記載したパッケージをpipでインストールしていきます。
```terminal
pip install black flake8 isort mypy
```

また、「2.」でやる内容の、pyproject.tomlファイルを使って設定する場合は、flake8だけでは無理なので、
もう一つ必要なパッケージをインストールします。
```terminal
pip install pyproject-flake8
```
<br>

### 2. 各種設定
「**pyproject.toml**」ファイルを用意することで、ツール一つ一つファイルを作成する必要がなくなり、一つのファイル内に設定を全てまとめることができます。

```pyproject.toml
[tool.black]
line-length=120

[tool.flake8]
max-line-length=120
ignore=""
exclude=".venv/"

[tool.isort]
include_trailing_comma = true
line_length = 120
multi_line_output = 3
use_parentheses = true

[tool.mypy]
install_types = true
non_interactive = true
```
▪️ black
[ line-length ]...1行あたりの文字数を120にします。
それ以外は既存の仕様を使います。

▪️ flake8
[ max-line-length ]...black同様に1行あたりの文字数を120にしてます。
[ ignore ]...解析に含ませたくないエラーコードを入れることができます。今回は全てのパターンを解析させます。

https://qiita.com/KuruwiC/items/8e12704e338e532eb34a#e401--multiple-imports-on-one-line

[ exclude ]...解析に含ませたくないフォルダ・ファイルを設定できますので、「.venv」を入れておきます。

▪️ isort
[ include_trailing_comma ]...複数行のインポート時に末尾にカンマをつけるように設定します
[ line_length ]...black同様に120にします
[ multi_line_output ]...複数行になった時の変換方法を指定します。複数パターンありますが、3番に設定します
[ use_parentheses ]...改行をするときにバックスラッシュではなくカンマを使用するに設定させます

▪️ mypy
[ install_types ]...型情報が欠けているライブラリを使用するときに、mypyが型チェックをできるようにするためにスタブファイルをインストールしても良いようにします。
[ non_interactive ]...install_typesに付随する設定で、スタブファイルをインストールするタイミングでユーザーに確認を求めず自動でインストールしてくれるようにします

<br>

### 3. 問題なく動くかチェック
#### ▪️ black
以下を実行。
```terminal
black --diff .
```
![スクリーンショット 2023-12-03 15.42.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/6907b612-85c1-f269-8297-02f7aef890c9.png)
「--diff」をつけることで、フォーマットする時の差分を出してくれます。
ただし、実際にコードを整形してくれるわけではないので、注意。

自動で整形して欲しい時は、
```terminal
black .
```
![スクリーンショット 2023-12-03 15.42.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/f699ae05-6042-bc44-0295-a0b40572b3ad.png)
これでコードを整形してくれます。
もし特定のファイルだけにblackを実行させたい場合は、「.」をファイルパスかフォルダパスにすればいけます。

#### ▪️ flake8
以下を実行
```terminal
pflake8 .
```

![スクリーンショット 2023-12-03 16.34.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/d89f9062-7bea-cb82-989a-c151ce64bf2f.png)

エラーがある場合はスクショのように表示されます。
もし全てのファイルで問題なければ、何も表示されません。

#### ▪️ isort
以下を実行
```terminal
isort .
```
![スクリーンショット 2023-12-03 16.38.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/67a98a45-d2d0-257b-566d-a1b70961a1a4.png)

import文を整形してくれました。

<br>

もし整形して欲しくない場合は以下のようにします。
```terminal
isort --check-only .
```
![スクリーンショット 2023-12-03 16.42.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/9daed73a-a310-6eec-792b-aa3355dde1f0.png)

#### ▪️ mypy
以下を実行
```terminal
mypy --ignore-missing-imports .
```
![スクリーンショット 2023-12-03 16.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/f2b1448d-85e8-5664-d233-607b4d21d23e.png)
型チェックをするために追加で必要なパッケージを自動でインストールしています。
また、型エラーがあった箇所と内容が表示されてるので問題なく動いてます。

<br>

# 自動化させる
4つのツールをプロジェクトに導入することができましたが、毎回4つのコマンドを叩くのは面倒です。
また、実行自体忘れる可能性があるので、自動で動くようにします。

Pre-commitを使うことで、commitする前にファイルに設定したコマンドを実行してくれます。
<font color="red">範囲スコープはcommit対象のファイルだけなので、プロジェクト全体に向けて実行されるわけではないので注意</font>

全て問題なければそのままcommitしますが、エラーが起きるとcommitされないので、実行忘れのリスクがなくなります。

### 1. プロジェクトに導入
```
pip install pre-commit
```

<br>

### 2. 設定ファイルの作成
「**.pre-commit-config.yaml**」を作成し、ファイル内に以下を記載。
```.pre-commit-config.yaml
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
  - repo: local
    hooks:
      - id: black
        name: code format
        entry: black .
        language: system
        types: [file, python]

      - id: isort
        name: import sort
        entry: isort .
        language: system
        types: [file, python]

      - id: pflake8
        name: code lint
        entry: pflake8 .
        language: system
        types: [file, python]

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.6.1
    hooks:
      - id: mypy
        args: [--ignore-missing-imports]
```
black→isort→pflake8→mypyの順番に実行してくれます。

black・isort・pflake8はローカルに入れてるパッケージを使って実行させますが、mypyはmirrors-mypyを使って実行させます。（ローカルのだと上手くいかなかったため）

<br>

### 3. Git Hooksスクリプトをインストール
```terminal
pre-commit install
```

<br>

### 4. 問題なく動くかチェック
試しにimport文をぐちゃぐちゃにしてcommitしてみました。
![スクリーンショット 2023-12-03 17.06.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/cc1c3f81-b14c-9b44-b3a7-beb6ad843395.png)
「import sort」がFailedになって、commitが通りませんでした。

<br>

綺麗に直して再度実行してみると、
![スクリーンショット 2023-12-03 17.03.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/9100db66-3275-d77e-bb1d-a94094847884.png)
全て通ってcommitできました。

<hr>