🏒

VSCode(Cursor)とFastAPIでデバッガを使う

2024/11/26に公開

概要

FastAPIのVSCodeでの開発時にデバッガを使う方法をまとめます。
PyCharmなど他のエディタでもデバッガは使用できますが、本記事では割愛し、VSCodeとCursorでのデバッガ方法をまとめます。

GitHub: https://github.com/hosimesi/code-for-techblogs/tree/main/fastapi_docker_debugger

デバッガの種類

Pythonでデバッガを使用したい場合、以下の2つが候補に上がってくると思います。

標準デバッガ (pdb)

Pythonに組み込まれている基本的なデバッガであり、コマンドラインで使用する方法とコード上に埋め込んで使用する方法の2種類があります。コマンドラインの場合、以下のようにモジュールを指定し、pdbに用意されているコマンドを使用して1行ずつ確認することができます。比較的小さなアプリケーションに向いているかと思います。

$ uv run python -m pdb pdb_cli_sample.py
$ uv run python -m pdb pdb_cli_sample.py                                     
> fastapi_docker_debugger/pdb_cli_sample.py(1)<module>()
-> def main() -> int:
(Pdb) n
> fastapi_docker_debugger/pdb_cli_sample.py(8)<module>()
-> if __name__ == "__main__":
(Pdb) n
> fastapi_docker_debugger/pdb_cli_sample.py(9)<module>()
-> result = main()
(Pdb) s
--Call--
> fastapi_docker_debugger/pdb_cli_sample.py(1)main()
-> def main() -> int:
(Pdb) s
> fastapi_docker_debugger/pdb_cli_sample.py(2)main()
-> a = 1
(Pdb) p a
*** NameError: name 'a' is not defined
(Pdb) n
> fastapi_docker_debugger/pdb_cli_sample.py(3)main()
-> b = 2
(Pdb) p a

コードに埋め込む方法はコードに直接pdbをimportし、pdb.set_trace()をブレークポイントとして追加します。

import pdb


def main() -> int:
    a = 1
    b = 2
    pdb.set_trace()
    c = a + b
    return c


if __name__ == "__main__":
    result = main()

実行すると、先ほどと同じCLIの入力待ちになるので、あとはpdbのコマンドに従ってデバッグしていきます。pdb.set_trace()はPythonの組み込み関数であるbreakpoint()でも代替可能です。

Editorのデバッガ

IDEではGUIを利用したデバッグが可能で、ブレークポイントの設定や変数の監視が容易になります。VSCodeやCursorではlaunch.jsonというファイルにデバッガの設定を書いて起動します。内部的にはdebugpyが使われています。
debugpyは、Microsoftが開発しているPython用のDebug Adapter Protocolです。IDEをクライアントとして、debugpyがPythonプログラム内で動作し、デバッグ用のサーバーとして動作します。
これにより、ローカル環境のみならずリモート環境でも同様にデバッグを行えるようになります。

VSCodeでデバッグをしたい場合、該当ファイルを開き、ブレークポイントをつけてPython Debugger: Debug Python Fileを押すとデバッグが始まります。

そして変数名にカーソルを当てると変数の値が確認できます。こうすることで一発で全ての変数の値をGUIで確認することができます。

FastAPIと通常のスクリプトとの違い

FastAPIでも通常のスクリプトと同様に、pdbやVSCodeのデバッガを使うことができますが、そのままでは使いづらい場合があります。チーム開発では、Docker Composeを使ってDocker環境を利用したり、Dev Containerを使用したり、ローカル環境で直接サーバーを起動したりすることが多いです。
また、通常のPythonスクリプトと異なり、Webフレームワークを使用したアプリケーションはサーバーとして起動し、HTTPリクエストを受け取って処理します。このサーバーは、uvicorngunicornなどのアプリケーションサーバーを介して動作します。そのため、デバッガを直接アタッチするには、いくつかの工夫が必要になります。

事前準備

まずは簡単なFastAPIのアプリケーションを立てます。パッケージマネージャーとしてはuvを使用しています。

  1. 環境の構築
$ uv init
$ uv add fastapi
  1. アプリケーションの作成
    好きなeditorでFastAPIのコードを作成します。
$ editor main.py
import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def health():
    a = 1
    b = 2
    return {"Hello": "World", "result": a + b}


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

FastAPIでデバッガを使う

FastAPIアプリケーションでデバッガを使用する方法を、以下の3つのケースに分けて解説します。

  • 直接使用する
  • Docker Composeで使用する
  • DevContainerで使用する

どの場合でもlaunch.jsonが必要になります。自身が使いたいものに合わせてlaunch.jsonをカスタマイズしたりパスを変更してください。

直接使用する

ローカルのPython環境を直接使用するには、Pythonのパスをuvによって作られた環境を通す必要があるので、以下のようにdefaultInterpreterPathにパスを通します。この辺りのパスは自身の環境に合わせて修正してください。

# launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: FastAPI with uv",
            "type": "debugpy",
            "request": "launch",
            "module": "uvicorn",
            "args": [
                "main:app",
                "--host",
                "0.0.0.0",
                "--port",
                "8000",
                "--reload"
            ],
            "cwd": "${workspaceFolder}/fastapi_docker_debugger",
            "jinja": true
        }
    ]
}
# settings.json
{
    "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"
}

そして、Python Debugger: Debug using launch.jsonで実行します。

あとは同じようにブレークポイントをつけると、リクエストがそこまで到達した時にデバッグが可能になります。

Docker Composeで使用する

Dockerで使用するには、Docker環境内で動いているサーバからデバッグサーバにリクエストが通るようにする必要があります。debugpyを使うためuv経由でinstallしておきます。

$ uv install debugpy

そして、uvのFastAPIプロジェクトに従って、Dockerfileを作成します。

FROM python:3.13-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
COPY . /app

WORKDIR /app
RUN uv sync --frozen --no-cache

そして、compose.yamlも作成します。

services:
  fastapi-docker-debugger:
    build:
      context: .
      dockerfile: docker/Dockerfile
    command: ["/app/.venv/bin/python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    container_name: fastapi-docker-debugger
    ports:
      - "8000:8000"
      - "5678:5678"
    expose:
      - "5678"
    volumes:
      - .:/app
    environment:
      - PYTHONUNBUFFERED=1
      - PYTHONDONTWRITEBYTECODE=1

ここで、debugpyを5678ポートに指定します。次にlaunch.jsonを準備します。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: FastAPI in Docker",
            "type": "debugpy",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/fastapi_docker_debugger",
                    "remoteRoot": "/app"
                }
            ]
        }
    ]
}

ここではattatch形式でdebugpyを起動させ、リモートの/appと5678番ポートで通信するように設定します。これらを準備できたらコンテナを起動します。

$ docker compose up --build

そして先ほどまでと同様にPython Debugger: Debug using launch.jsonでデバッガを起動し、ブレークポイントを仕込んでcurl http://localhost:8000をすると以下のようにデバッグすることができます。

DevContainerで使用する

DevContainerを使用する場合もDocker環境を使うので、上記とほぼ同じですが、devcontainer.jsonのみ準備します。ここでは、compose.yamlとservice名を指定しています。

{
    "name": "FastAPI Dev Container",
    "dockerComposeFile": "../compose.yaml",
    "service": "fastapi-docker-debugger",
    "workspaceFolder": "/app"
}

そして、Reopen in Containerでコンテナ環境でフォルダを開きます。するとすでにFastAPIサーバが立っているので、これまでと同様にデバッガを起動します。そして先ほどと同様にブレークポイントを仕込んでcurl http://localhost:8000をすると以下のようにデバッグすることができます。

終わりに

今回はローカルのDocker Container内のFastAPIでデバッガを使う方法をまとめました。
printデバッグを卒業し、デバッガを使うことで効率的な開発ができると思います。

参考

https://fastapi.tiangolo.com/tutorial/debugging/
https://qiita.com/N700A/items/0b57a75a1016f76c8358
https://github.com/microsoft/debugpy
https://qiita.com/yagrush/items/e46ab11b22495cd2c0a0

Discussion