Python開発環境構築手順（VSCode, Docker, Poetry, isort, black, flake8, pytest）

# はじめに
* Python開発環境を整備し直したときのメモです
* コードは[こちら](https://github.com/nokoxxx1212/python-setup)に配置しています

# どういう開発環境を作るか
* VSCode・Docker上で実行する（ローカルでの実行を想定）
* パッケージ管理はPoetryを利用する
* linter, formatterはisort, black, flake8を利用する
    * コマンドだけでなくVSCodeからも利用する
* テストはpytestを利用する
* Jupyter Notebookも利用可能にしておく

# セットアップ手順
## 事前準備【ローカル】
* VSCode・Dockerのインストール
* VSCode拡張機能Remote Developmentのインストール

## 設定ファイルの配置
* （参考）フォルダ構成

```
.
├── .devcontainer
│   └── devcontainer.json
├── .vscode
│   ├── launch.json
│   └── settings.json
├── Dockerfile
├── pyproject.toml
├── src
│   └── sample.py
└── tests
    └── test_sample.py
```

* .devcontainer/devcontainer.json
    * extensionsに記載したVSCode拡張機能はコンテナに強制インストールされる（推奨でなく）
    * 開発用のDockerfileを.devcontainer/配下に置くこともある
    * （参考）[【2022年版】VSCode + Dockerで最強のPython開発環境を構築する【Remote Development】](https://www.true-fly.com/entry/2021/12/08/073000#VSCode-%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BDRemote-Development%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

```json:.devcontainer/devcontainer.json
{
    "name": "Python39docker",
    "dockerFile": "../Dockerfile",
    "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "njpwerner.autodocstring",
        "mosapride.zenkaku",
        "streetsidesoftware.code-spell-checker"
    ],
    "forwardPorts": [8888]
}
```

* .vscode/launch.json
    * （参考）[【2022年版】VSCode + Dockerで最強のPython開発環境を構築する【Remote Development】](https://www.true-fly.com/entry/2021/12/08/073000#VSCode-%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BDRemote-Development%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

```json:.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python39docker",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "internalConsole"
        }
    ]
}
```

* .vscode/settings.json
    * isort, black, flake8がVSCodeから自動で実行されるようにする
    * （参考）[VSCodeでPythonのコードベースにLintとFormatを実行する](https://blog.mine6ra.com/posts/lint-format-python/)

```json:.vscode/settings.json
{
    //isort
    "editor.codeActionsOnSave": {
        "source.organizeImports": true
    },
    //black
    "editor.formatOnSave": true,
    "python.formatting.provider": "black",
    //flake8
    "python.linting.lintOnSave": true,
    "python.linting.enabled": true,
    "python.linting.pylintEnabled": false,
    "python.linting.flake8Enabled": true,
    //autoDocstring
    "autoDocstring.docstringFormat": "google"
}
```

* Dockerfile
    * --no-root: デフォルトで src をインストールするので、回避する
    * （参考）[Dockerコンテナにパッケージ管理ツールpoetryを使ってPythonの分析環境を構築する方法](https://qiita.com/yolo_kiyoshi/items/332ae902aeb730fbd068)

```dockerfile:Dockerfile
FROM python:3.9

# install python package
COPY pyproject.toml ./
RUN pip install poetry
RUN poetry config virtualenvs.create false \
    && poetry install --no-root

# mount dir
RUN mkdir -p /opt/mnt
WORKDIR /opt/mnt

# expose port
EXPOSE 8888
```

* pyproject.toml
    * packages: sys.pathに自動的に含まれるようになる（tests配下からのパスなどで利用する）
    * （参考）[その設定、pyproject.tomlに全部書けます](https://data.gunosy.io/entry/linter_option_on_pyproject)
    * まず手動で `poetry add pandas` とかで試してからファイルに追記する

```toml:pyproject.toml
[tool.poetry]
name = "sample"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
packages = [
    { include = "src" },
]

[tool.poetry.dependencies]
python = "^3.9"
pandas = "^1.5.1"
isort = "^5.10.1"
black = "^22.10.0"
pyproject-flake8 = "^5.0.4.post1"
pytest = "^7.2.0"
jupyterlab = "^3.5.0"

[tool.isort]
profile = "black"
line_length = 120

[tool.black]
line-length = 120

[tool.flake8]
max-line-length = 120
ignore = "E203,"

[tool.pytest.ini_options]
testpaths = ["tests",]
```

* src/sample.py
    * サンプルコード
    * （参考）[pytest](https://rinatz.github.io/python-book/ch08-02-pytest/)

```python:src/sample.py
import sys


def is_prime(n: int) -> bool:
    if n <= 1:
        return False

    if n == 2:
        return True

    if n % 2 == 0:
        return False

    i = 3

    while i * i <= n:
        if n % i == 0:
            return False

        i += 2

    return True

if __name__ == '__main__':
    args = sys.argv
    n = int(args[1])
    print(is_prime(n))
```

* tests/test_sample.py
    * サンプルのテストコード
    * （参考）[pytest](https://rinatz.github.io/python-book/ch08-02-pytest/)

```python:tests/test_sample.py
from src.sample import is_prime


def test_is_prime():
    assert not is_prime(1)
    assert is_prime(2)
    assert is_prime(3)
    assert not is_prime(4)
    assert is_prime(5)
    assert not is_prime(6)
    assert is_prime(7)
    assert not is_prime(8)
    assert not is_prime(9)
    assert not is_prime(10)
```

## コンテナ起動・接続
* コンテナ起動

```bash
$ docker build -t python39docker .
$ docker run -itd -v $(pwd):/opt/mnt -p 18888:8888 python39docker
```

* コンテナにVSCodeからの接続
    * VSCode左下のリモートウィンドウ → Attach to Running Container

→ コンテナ側のVSCodeが起動するので、開発する

## その他
* linter, formatter実行

```bash
$ poetry run isort src tests
$ poetry run black src tests
$ poetry run flake8 src tests
```

* テスト実行

```bash
# packageを読み込んでパス設定
$ poetry install
# テスト実行
$ poetry run pytest src tests
```

* Jupyter Notebook起動

```bash
$ jupyter notebook --allow-root --port=8888 --ip=0.0.0.0 &
# jupyter lab --allow-root --port=8888 --ip=0.0.0.0 &
```

* GitHub Copilot
    * 契約、VSCode拡張
        * [【GitHub Copilot】登録方法の解説と試してみた](https://chigusa-web.com/blog/github-copilot/)
    * -> コメントを書いて補完、チャットで単体テスト生成