FastAPIとPythonの型ヒントが反映されたDocker環境を作ってそこにVS CodeのRemote Containerから接続する構築手順（2022年版）

# 1. はじめに

FastAPIの開発環境をPythonの型付け（型ヒント）の恩恵を厳し目に受けながら開発したい。
そしてそれらの設定が反映されたDockerの開発環境をつくってホストの環境を汚さないようにしたい。
さらにVS CodeのRemote Containerを使うことでDockerの開発環境に接続して開発できる状態にしたい。

という動機から始まっています。

## 1-1.背景

上記を実現するためにFastAPIの環境構築について調査していると、「**個人的にこの設定がスタンダートな最小構成だよね**」をまとめるに苦労したのでこの際まとめておくことにしました。

以下が反映される最小構成を目指します。

- Pythonで型付け（型ヒント）の恩恵をうける設定が入っていること
- FastAPIの[自動チューニングの設定](https://fastapi.tiangolo.com/ja/deployment/docker/#tiangolouvicorn-gunicorn-fastapi)が入っていること
- 日本語化やタイムゾーンなどの基本的な設定がDockerイメージに含まれていること
- これらをVS CodeのRemote Containerで開発できる設定になっていること

## 1-2. 前提条件

- Remote ContainerはVS Codeにインストール済み
- Docker Descktopはインストール済み
- 今回はPython 3.9
    - FastAPIの公式イメージの最新版が執筆時点で3.9なので

# 2. 構築手順

とにかく動かしたい方は、以下の手順をコピペすれば動きます。

## 2-1. ディレクトリ構成

以下のように配置します。`fastapi-test`がworkspaceになるディレクトリです。

```
fastapi-test
├─ .devcontainer
    └─ devcontainer.json
├─ app
    └─ main.py
├─ .dockerignore
├─ Dockerfile
└─ requirements.txt
```

## 2-2. 各ファイルの内容

### devcontainer.json

`devcontainer.json` はRemote Containerを使うときの設定ファイルです。
公式ドキュメントどおり `.devcontainer/devcontainer.json` に配置します。

VS CodeのRemote Containerを立ち上げたときに、以下の動作になるように設定しています。
- Lintが動作すること
- 型ヒントが厳しめに動作すること
- ただし外部ライブラリについては余計な型チェックが入らないように動作すること
    - TypeScriptと違って型定義が提供されていないライブラリは多い
    - `mypy.ini`をおいても解決しないので `diagnosticSeverityOverrides` に設定を書くのがミソです

また、`root`で作業できないように `appuser` でコンテナに接続するようにします。
後述のDockerfile内で`appuser`を作成しています。


```json
{
	"name": "fastapi-test",
	"build": {
		"dockerfile": "../Dockerfile",
		"context": "..",
	},
    "runArgs": [ "--name", "fastapi-test"],
	"settings": { 
		"python.linting.enabled": true,
		"python.linting.pylintEnabled": true,
		"python.linting.pylintArgs": [
			"--disable", "E1101,E0213,R0201",
			"--extension-pkg-whitelist", "pydantic",
		],
		"python.linting.mypyEnabled": true,
		"python.analysis.typeCheckingMode": "strict",
		"python.analysis.diagnosticSeverityOverrides": {
			"reportMissingTypeStubs": "none",
			"reportUnknownParameterType": "none",
  			"reportUnknownMemberType": "none",
  			"reportUnknownArgumentType":  "none",
			"reportUnknownVariableType": "none"
		}
	},
	"extensions": [
		"ms-python.python",
        "njpwerner.autodocstring",
		"ms-python.vscode-pylance",
	],
	"postCreateCommand": "pip install -U mypy pylint",
	"remoteUser": "appuser"
}
```

### main.py

[公式ドキュメント](https://fastapi.tiangolo.com/ja/deployment/docker/#fastapi) の内容をそのまま拝借します。

```python
from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}
```

### .dockerignore

主にpython周りの余計なファイルなどがビルドに含まれないように設定しておきます

```
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache
```

### Dockerfile

冒頭のとおり個人的にスタンダートな最小構成を書いています

```Dockerfile
# 公式のイメージ ちなみに80がEXPOSEされている
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9

# python周りの設定
# pycを生成しない、および 標準出力と標準エラー出力がすぐに出力されるようにする
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1

# 日本語化周りの設定
RUN apt-get update && apt-get install -y locales && locale-gen ja_JP.UTF-8
ENV LANG=ja_JP.UTF-8 TZ=Asia/Tokyo

# お好みで
# RUN apt-get install -y vim less procps libssl-dev curl

# 専用のユーザーを作成してbashに変更する
RUN useradd --create-home appuser && chsh -s /bin/bash appuser
USER appuser
WORKDIR /home/appuser

# パッケージをインストール
COPY ./requirements.txt .
RUN pip install --upgrade pip setuptools \
&& pip install --no-cache-dir -r ./requirements.txt

# アプリケーションのソース
COPY ./app/ ./

```

### requirements.txt

```
お好みで
```

# 3. 動作確認

## 3-1. VS CodeのRemote Containerを起動する

`fastapi-test`ディレクトリをVS Codeで開いたら画面左下にある緑色のアイコンを押して、メニューの中から `Open Folder in Container` を開きます。

![スクリーンショット 2022-04-24 11.59.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549788/37cb290b-2bf5-1ba0-43af-c0b6d5a1efb9.png)

Dockerイメージのビルドが始まるのでしばらく待ちます。

ビルドが完了すると、Pylanceの拡張を有効にするためのポップアップが表示されるので `Yes` を押してVS Codeを開き直します。

![スクリーンショット 2022-04-24 12.03.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549788/f58a73e1-8e72-4a91-54dc-95813c991dd6.png)

## 3-2. アプリケーションを起動する

VS Codeからターミナルを開いて（ちなみにBashになっています）、`uvicorn app.main:app --host 0.0.0.0 --reload` でアプリケーション（`main.py`）を起動します。

![スクリーンショット 2022-04-24 12.07.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549788/109cf00c-7482-675e-c81b-b46f73a8f23d.png)

リンクを開いて  `{"Hello":"World"}` が表示されていればOKです。
また、　[FastAPIの公式ドキュメントのとおり](https://fastapi.tiangolo.com/ja/deployment/docker/#_2)、APIがたたけることも確認しておきます。

もちろん[公式の通り/docsを開けば](https://fastapi.tiangolo.com/ja/deployment/docker/#tiangolouvicorn-gunicorn-fastapi)Swagger UIのドキュメントを確認できます。

![スクリーンショット 2022-04-24 16.49.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549788/2913266d-01ad-5eec-5fa1-c156917b71a3.png)

ちなみに、↑ではホストの8000番ポートが接続していますが、これはRemote Containerが自動的にフォワーディングする機能があるためです（便利ですね）。

![スクリーンショット 2022-04-24 16.54.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549788/38d9fb2f-beec-27e3-bf17-82c6259362c2.png)

以上