【Python】VSCodeからDocker上のFastAPIをデバッグできるようにする

## はじめに
VSCodeを使って、FastAPIのデバッグができる方法を調査しました。
本記事では、デバッグ環境構築の方法をご紹介します。

## 動作イメージ
下記のGifのようにVSCodeのブレークポイントでコードを止めることができます。また、ホバーした変数の値を参照したり、書き換えることができます。

![debugpy_mp動作の様子.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349189/78e92dd2-8272-12f2-2ea6-8fb5f3e31525.gif)

![debugpy_マウスホバー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349189/a9c9eb5c-65ee-22b4-1544-894b8069942c.png)


## 環境設定の概要

環境構築手順の要点は下記の通りです。
1. 対象となるFastAPIプロジェクトのrequirements.txtに`debugpy`を追加する
1. debugpyを起動できるように、docker-compose.ymlを設定する
1. VSCodeからアクセスできるように、launch.jsonを作成する

### フォルダ構造
今回のサンプルのディレクトリ構造は下記のとおりです。

```
　（VSCodeプロジェクトルート）
  ├ docker-compose.yml
  ├ backend
    ├ app
      ├ Dockerfile
      ├ requirements.txt
      ├ main.py # FastAPI実行ファイル
```

### 環境設定の手順
#### 1. requirements.txt に debugpy を追加する

```py: requirements.txt
fastapi
debugpy # 追加
```

#### 2. debugpyを起動できるように、docker-compose.ymlを設定する

```yaml: docker-compose.yml
  backend:
    build:
      context: ./backend
      dockerfile: ./Dockerfile
    volumes:
      - ./backend/:/backend
    env_file: ./backend/.env
    working_dir: /backend/app
    command: sh -c "python3 -m debugpy --listen 0.0.0.0:5678 -m uvicorn main:app --reload --proxy-headers --host='0.0.0.0' --port=8000"
    ports:
      - "8000:8000"
      - "5678:5678"
    tty: true
    stdin_open: true
```

下記のdiffは、通常のdocker-compose.ymlとの差分を表したものです。通常の設定と異なる箇所は、command, portsの指定です。

```diff_yaml
    env_file: ./backend/.env
    working_dir: /backend/app
-    command: sh -c "uvicorn main:app --reload --proxy-headers --host='0.0.0.0' --port=8000"
+    command: sh -c "python3 -m debugpy --listen 0.0.0.0:5678 -m uvicorn main:app --reload--proxy-headers --host='0.0.0.0' --port=8000"
    ports:
-      - "8080:8000"
+      - "8000:8000"
+      - "5678:5678" # debugpy用のポートを追加
    tty: true
```
- command: `python3 -m debugpy --listen 0.0.0.0:5678 -m` を先頭につけます
- ports: `5678`ポートをフォワーディングします

#### 3. VSCodeからアクセスできるように、launch.jsonを作成する
launch.json は、プロジェクト内の`.vscode/`ディレクトリ配下に作成します。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "debugpy",
            "request": "attach",
            "name": "Docker (FastAPI)",
            "connect": {
                "host": "localhost",
                "port": 5678  // docker-compose.ymlの ports の定義と同じにします
            },
            // docker-compose.ymlの volumes で定義した構造と同じにするよう設定します
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/backend",
                    "remoteRoot": "/backend"
                }
            ]
        }
    ]
}
```

上記のlaunch.jsonファイルの中で要点は下記のとおりです。
- host
    - localhostとします
- port
    - ポートフォワーディングしたdebugpyのport番号を指定します
- pathMapping
  - localRoot: ホスト側の起点ディレクトリです。サンプルコードでは、プロジェクト内の`/backend`ディレクトリを指定しています。
  - remoteRoot: コンテナ内の起点ディレクトリです。サンプルコードでは、`/backend`ディレクトリを指定しています。
これらの設定は、docker-compose.ymlのvolume指定を参考に記述すると良いです。
- 補足
    - launch.jsonの"request"オプションには、connectやlistenなどのオプションがあります。詳細は下記のリンクを御覧ください。
        - [Update launch.json handling to support "listen" and "connect" · Issue #10437 · microsoft/vscode-python](https://github.com/microsoft/vscode-python/issues/10437)

### 使ってみる
ここまでの手順で、VSCodeからdebugpyを利用する準備は終了です。動作確認には以下の手順を実行してください。
1. docker-compose up を実行し、FastAPIコンテナを起動する
1. VSCodeを開き、適当な行番号をマークする（赤印をつける）
1. VSCodeサイドメニューの`実行とデバッグ`をクリックし、上部の`▷ Docker (FastAPI)`をクリックする
1. OpenAPIなどからFastAPIにリクエストを飛ばし、指定した行で処理が止まることを確認する

![debugpy_説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349189/887a2793-15ea-1b07-76f8-be6c1bfc640a.png)


**Happy Hacking!**

### ローカルファイルの変更がDockerコンテナ内に反映されないときは？（mac環境など）
docker-compose.ymlに下記の環境変数を追加すると、FastAPIが強制リロードしてくれます。
※ ただし反映まで3秒程度のラグが生じるようです。

```yaml
    environment:
      - WATCHFILES_FORCE_POLLING=true
```

#### 参考記事
https://qiita.com/yagrush/items/e46ab11b22495cd2c0a0

https://github.com/fastapi/fastapi/issues/5808#issuecomment-1361531145