【VSCode】Pythonのデバッグ環境整備してみた【Docker】
IT技術
Python(FastAPI)の開発環境を作っていく上で、デバッグ環境をどう作るの?ってなったので色々調べてみました。
この記事では「VSCodeからデバッグサーバを起動し、デバッグを実行できる状態」を目指します。
環境構成は以下です。
- インフラ:Docker
- エディタ:VSCode
- 言語:Python(FastAPI)
Dockerコンテナ作成
FastAPIのコンテナ(とMySQLコンテナ)を作るためのファイルです。
- Dockerfile
1
2FROM python:3.10-bullseye
3ENV PYTHONUNBUFFERED=1
4ENV PYTHONPATH=/src
5
6WORKDIR /src
7
8# pipを使ってpoetryをインストール
9RUN pip install poetry
10
11# poetryの定義ファイルをコピー
12COPY pyproject.toml poetry.lock ./
13
14# poetryでライブラリをインストール (pyproject.tomlが既にある場合)
15RUN if [ -f pyproject.toml ]; then poetry install --no-root; fi
16
17
18# uvicornのサーバーを立ち上げる
19ENTRYPOINT ["poetry", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--reload"]
- docker-compose.yml
1version: '3.9'
2services:
3 api:
4 build: .
5 volumes:
6 - .:/src
7 ports:
8 - "${API_PORT}:8000" # .envに環境変数で任意に設定(その他も同様)
9 container_name: api
10 env_file:
11 - .env
12 environment:
13 - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE}
14 db:
15 image: mysql:8.0
16 platform: linux/arm64
17 volumes:
18 - db-data:/var/lib/mysql
19 ports:
20 - "${DB_PORT}:3306"
21 container_name: db
22 env_file:
23 - .env
24volumes:
25 db-data:
- pyproject.toml
1[tool.poetry]
2name = "sample-app"
3version = "0.1.0"
4description = ""
5authors = ["Your Name "]
6readme = "README.md"
7
8[tool.poetry.dependencies]
9python = "^3.9"
10fastapi = "^0.111.0"
11uvicorn = {extras = ["standard"], version = "^0.29.0"}
12tortoise-orm = "^0.20.0"
13aiomysql = "^0.2.0"
14
15
16[tool.poetry.group.dev.dependencies]
17pytest-asyncio = "^0.23.6"
18httpx = "^0.27.0"
19pre-commit = "^3.7.1"
20
21[tool.pytest.ini_options]
22asyncio_mode = "auto"
23
24[build-system]
25requires = ["poetry-core"]
26build-backend = "poetry.core.masonry.api"
これらをプロジェクトのルートに置いた上で以下を実行し、コンテナを作成・起動します。
docker compose build
docker compose up -d
VSCodeセットアップ
Microsoft製の拡張機能、Pythonをインストールします。
この拡張機能を入れると付属でdebugpyを使ったデバッグ用の拡張機能Python Debuggerも使えるようになります。
デバッグサーバの構築
ポート追加
デバッグサーバが稼働するポートとリクエストを待ち受けるポートを追加します。
docker-compose.ymlに追記します。(.envも忘れずに)
1version: '3.9'
2services:
3 api:
4 build: .
5 volumes:
6 - .:/src
7 ports:
8 - "${API_PORT}:8000"
9 - "${DEBUGGER_PORT}:5678" # これを追加
10 - "${DEBUG_API_PORT}:8001" # これを追加
11 container_name: api
12 env_file:
13 - .env
14# 略
debugpyインストール
プロジェクトにdebugpyをインストールします。
コンテナ内からデバッグサーバを起動し、リッスンするために使います。(後述)
コンテナに入って以下コマンドを実行します。
poetry add --group dev debugpy
vscodeデバッグ起動設定
ここがこの記事で一番肝心箇所になります。
.vscodeディレクトリに以下のファイルを作成します。
- launch.json
1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Python: Remote Attach",
6 "type": "debugpy",
7 "request": "attach",
8 "connect": {
9 "host": "localhost",
10 "port": 5678
11 },
12 "pathMappings": [
13 {
14 "localRoot": "${workspaceFolder}",
15 "remoteRoot": "/src"
16 }
17 ],
18 "preLaunchTask": "Start Uvicorn with Debugpy from local",
19 }
20 ]
21}
- tasks.json
1{
2 "version": "2.0.0",
3 "tasks": [
4 {
5 "label": "Start Uvicorn with Debugpy from local",
6 "type": "shell",
7 "command": "docker exec -it api bash -c 'poetry run python -m debugpy --listen 0.0.0.0:5678 --wait-for-client --log-to-stderr -m uvicorn api.main:app --host 0.0.0.0 --port 8001'",
8 "isBackground": true,
9 "presentation": {
10 "reveal": "always",
11 "panel": "dedicated"
12 },
13 "problemMatcher": {
14 "owner": "custom",
15 "pattern": {
16 "regexp": "^$"
17 },
18 "background": {
19 "activeOnStart": true,
20 "beginsPattern": ".*",
21 "endsPattern": "wait_for_client()"
22 }
23 }
24 }
25 ]
26}
# 説明
launch.jsonには、debugpy拡張機能を使ったデバッグ起動の設定情報が記載されています。
- ローカルホストの5678番はコンテナの5678番と紐づくように設定しているので(.envにて)、VSCodeとDockerがここで繋がります
- ローカル(VSCodeのプロジェクトルート)のファイルはリモート(コンテナ)の/srcとマッピングしています
preLaunchTask
は、VSCodeのデバッグ起動前に実行したい処理を指定します。(tasks.jsonの内容)
tasks.jsonには、コンテナ内でデバッグ用のFastAPIをポート8001番で起動し、ポート5678番でクライアント(VSCode)からの通信を待機するコマンドが記載されています。
- problemMatcherにて、VSCode側にデバッグセッションが正常に開始したことを伝え、このpre-taskの終了をVSCodeに伝えます
いざ、デバッグ
これで準備は出来たのでデバッグを実行します。
ブレークポイントを設定し、そこで止まることを期待します。
- main.py
1from fastapi import FastAPI
2from fastapi.middleware.cors import CORSMiddleware
3from starlette.config import Config
4from starlette.datastructures import CommaSeparatedStrings
5
6config = Config("/src/.env")
7
8app = FastAPI()
9origins = config("CORS_ORIGINS", cast=CommaSeparatedStrings, default="")
10
11app.add_middleware(
12 CORSMiddleware,
13 allow_origins=origins,
14 allow_credentials=True,
15 allow_methods=["*"],
16 allow_headers=["*"],
17)
18
19@app.get("/hello")
20async def hello():
21 name: str = 'yamada'
22 return {f'message: hello { name } !!'} # ここにブレークポイントを設定
以下エンドポイントにGETでリクエストを送ります。
localhost:{設定したPort}/hello
指定した場所で処理がとまりました。
おわり
これまで所属していたプロジェクトでは既にデバッグ環境があり、あまり意識してこなかったところだったので勉強になりました。
これに留まらず、開発のストレスを減らしたり、効率化のためにも環境構築やインフラ周りは引き続き調査していきます。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
2022年7月に入社しました。開発未経験で未知の領域だらけですが、楽しく学びつつ、早く戦力になれるようにがんばります!