Poetryプロジェクトのテンプレートを作ってみた

# 概要
新しいプロジェクトを作るとき、毎回一から環境構築するのは面倒だし、ミスがあるかもしれないし、そんな反復作業をするより開発に時間を回したい！
ということで、Python用のパッケージ管理ツールの一つである「[Poetry](https://github.com/python-poetry/poetry)」を使ったPythonプロジェクトのテンプレートを作ってみた。
自分用ではあるけど、基本的な構成ではあるため他の人も使えるように公開している。

https://github.com/torippy1024/poetry-template

構成はざっと以下の通り。
* poetry (パッケージ管理ツール)
* flake8 (リンター)
* black (フォーマッター)
* pytest (テスト)
* taskipy (タスク定義)
* pyinstaller (実行ファイル化)
* .vscode/settings.jsonでファイル保存時自動フォーマット

## 本記事で説明すること
* つかいかた
* 内容解説

## 本記事で説明しないこと
* poetryのインストール手順 (poetryはインストール済であることが前提)
* poetry自体の使い方・解説
* 各ライブラリの詳細な解説


# つかいかた

## clone
GitHubに公開してるので基本的にcloneしてあとは良しなに。
`projectName`は好きなプロジェクト名に置き換えてください。

```shell
git clone https://github.com/torippy1024/poetry-template.git projectName
cd projectName
poetry install
```

## ディレクトリ構成
ディレクトリ構成は以下の通り。

```shell
┣ .vscode/           # vscode用の設定記述
┣ src/               # ソースコードはここに置く
┃   ┗ main.py        # エントリーポイント
┣ tests/             # テストコードはここに置く
┃   ┗ test_main.py
┣ .flake8            # flake8の設定
┣ .gitignore
┣ README.md
┣ poetry.lock
┗ pyproject.toml
```

`src/main.py`の`if __name__ == "__main__":`にメイン処理を書く。
テンプレートは足し算のサンプル。
```python:src/main.py
def addition(num1: float, num2: float):
    return num1 + num2


if __name__ == "__main__":
    print("Please enter two numbers.")

    try:
        num1 = float(input("num1: "))
        num2 = float(input("num2: "))
    except ValueError:
        print("Invalid input. Please enter number.")
        exit()

    result = addition(num1, num2)
    print(f"{num1} + {num2} = {result}")
```

後述するが、`task run`で`src/main.py`を実行できる。
```shell
> task run
Please enter two numbers.
num1: 5
num2: 3
5.0 + 3.0 = 8.0
```

`tests`フォルダにはテストコードを書く。
```tests/test_main.py
from src.main import addition


def test_addition():
    result = addition(2, 3)
    assert result == 5
```

`task test`でpytestによるテストを実行できる。
```shell
> task test
====================== test session starts =======================
platform win32 -- Python 3.10.0, pytest-7.2.1, pluggy-1.0.0        
rootdir: ...
collected 1 item

tests\test_main.py .                                        [100%] 

======================= 1 passed in 0.02s ========================
```


## 機能
`poetry shell`で仮想環境に入ったあと、以下のコマンドが使える。
* `task run`: src/main.py を実行
* `task lint`: flake8 で lint
* `task build`: pyinstaller で exe化
* `task build-one`: pyinstaller で exe化 (＋ 1つのファイルにまとめる) 
* `task test`: pytest 実行

また、vscodeでファイル保存時、自動フォーマットが動いてくれる。


# ちょっとした解説とカスタマイズのしかた
以下はもう少し構成が知りたい人と、一部カスタマイズして使いたいな～と思った人向け。

## リンター・フォーマッター
リンターはflake8、フォーマッターはblackを採用している。
選定はこちらの記事を参考にした。

https://qiita.com/sin9270/items/85e2dab4c0144c79987d

どちらの設定もデフォルトのままでいいかなと思っているが、flake8とblackのルールに一部競合するところがあるらしいので、その部分だけflake8の設定を変えてあげている。

https://github.com/psf/black/blob/main/docs/compatible_configs/flake8/.flake8

```:.flake8
[flake8]
max-line-length = 88
extend-ignore = E203
```

また、VSCodeでファイルを保存したとき、blackで自動フォーマットしてほしいため、vscodeの設定ファイルを以下のようにしている。

```json:.vscode/settings.json
{
  "python.formatting.provider": "black",
  "editor.formatOnSave": true
}
```


flake8をカスタマイズしたい場合は、以下の公式ドキュメントを参考に`.flake8`に追記すると良いだろう。

https://flake8.pycqa.org/en/latest/user/configuration.html

blackのカスタマイズは以下ご参考に。

https://black.readthedocs.io/en/stable/usage_and_configuration/index.html


## テスト
テストモジュールはpytestを採用。
選定は以下の記事を参考にした。

https://qiita.com/flcn-x/items/fcbbc2fb291b970290f2

詳しい解説は私自身まだ十分に使いこなせていないため省略するが、
pytestは、`test`から始まるファイルの`test`から始まる関数を対象にするらしいので注意。


## タスク定義
タスク定義にはtaskipyを使っている。
taskipyはPythonのタスクランナーである。
taskipyを使うと`pyproject.toml`にタスクを定義することができる。

例えば、今回作成したテンプレートの`pyproject.toml`の下には以下の記述がある。
```toml:pyproject.toml
[tool.taskipy.tasks]
run = "poetry run python src/main.py"
lint = "poetry run flake8 src --show-source --statistics"
build = "poetry run pyinstaller -y src/main.py"
build-one = "poetry run pyinstaller -y src/main.py --onefile"
test = "poetry run pytest"
```

このように`タスク名 = "実行したいコマンド"`としてあげると、`task タスク名`で`実行したいコマンド`が実行される。

`src/main.py`を実行するために`python src/main.py`などと打つことなく`task run`のように短いコマンドかつ意味のあるコマンドで実行できるためとても便利。
加えて、fastapiのプロジェクトの場合は`run = "poetry run uvicorn src.main:app"`と書き換えてあげたり、プロジェクトによってタスク名に合わせたコマンドに修正してあげることで、「どのプロジェクトでも実行したいときは`task run`！」と統一することができる。


## 実行ファイル化
こちらは利用できるケースが限られているが、ものによってはpyinstallerというモジュールを使うことで実行ファイル化(exe化)することができる。

実行ファイル化できてしまえばPython環境が要らなくなるので配布しやすくなる。
適切なPythonバージョンをインストールしてもらって、pipで必要なモジュールを全部インストールしていもらって…などせずとも、exeファイル一個投げてダブルクリックしてもらうだけで動くのだ。

実際にPythonで作ったものをpyinstallerを使ってexeファイル化して配布などもしてみたりしている。
（コードは全然大した内容じゃないが、ダウンロードしてすぐ使える形式で配布しているだけで、なんか中身があるように見える）

https://github.com/torippy1024/battery-watch

前述の通り、`task build`や`task build-one`でpyinstallerが走るように設定している。
実行すると、上手くいけば`dist/`ディレクトリの中にexeファイルが生成されるはずである。
特に`task build-one`はファイルを完全に一つのファイルにまとめてくれるので、基本的にこっちを使っている。（開発のときは`task build`の方が早いかも？）

poetry管理外のバイナリに依存していたり、fastapiなどの単純にsrc/main.pyを実行するわけではないサーバ系だったり、なんか必要なライブラリをpyinstallerが網羅してくれなかったり(transformers使っているプロジェクトで直面した)する場合は、色々な設定をしないと実行ファイル化できないので、そこは調べてがんばってください…。


# 最後に
自分用のテンプレートがあると、何か作りたくなったときに一瞬で環境ができるのでとても快適です。
みなさんも自分用のテンプレートを作ってみてはいかがでしょうか？

また、「このテンプレートのこの設定方法あまり推奨されないよ！」だったり、「こっちのライブラリの方がいいかも！」だったり、「このライブラリも追加で使ってみない？」などありましたら、ぜひコメントいただけると嬉しいです。