軽い気持ちでChatGPTを駆使してFastAPIの学習をしたら、結構苦戦した話

# 概要
2024年11月からChatGPTを使ってFastAPIの学習をしたら、何かと苦戦することが多かったので記録を残します。

学習した内容は下記Githubに公開しています。
https://github.com/dbd-fish/sample_FastAPI_system

あくまで私個人の意見であるため、参考にする際はご注意ください。
掲載内容に誤りがあれば教えてくださると嬉しいです。

## 動機
下記を目的として学習を始めました。
- モダンなフレームワークを学習することで、エンドエンジニアとしてのスキルを向上させる。
- Docker環境の構築を自分で0からやってみる
- 開発における生成AIの使い方を模索する。

## スキルレベル
FastAPIに関する私のスキルレベルは下記の通りです。
・LaravelなどでWeb系バックエンドエンジニアとして自走できるレベル。
・画面遷移図案がある状態から、DB設計やAPI設計からテストまでするレベル。
・Pythonの使用経験はあるが、モダンなテクニックにはあまり触れていない。
・FastAPIは昔チュートリアルを流し読みしただけ
・非同期処理は何となくわかっている程度(タスクとかasync awaitを何となく使える程度)


# 苦戦した点
下記にChatGPTで苦戦した点とコーディング面で苦戦した点をメモします。

## ChatGPTで苦戦した点
ChatGPT 4oの学習期間は2023年12月までであり、それ以降の情報をもとに対話する場合はChatGPT SearchをONにして対話する必要があります。
**ChatGPT SearchをONにすれば、常に最新情報を取得して対話できるから超便利かと思いきやそうではありませんでした。**
ちゃんと使い方を考えて使用しないと自分が振り回されてしまいます。

    
### 常に最新の回答を提示してくれるわけではない
FastAPIなど最新バージョンと旧バージョンで実装方法がことなるような題材をもとに会話すると、旧バージョンのコードを提示することが多いです。
例えばFastAPI 0.115.5で`@app.on_event("startup")`を含んだコードを提示してきたり、sqlalchemy2.0に対応したモデル定義を提示しなかったりします。  


### 直接的な答えがWeb上に存在しないと上手く機能しない
後述する「Pytest-asyncioを用いた非同期テストにおけるDBコネクションプーリング起因のイベントループエラー」について、Web上には直接的な回答がなくChatGPTに聞いてもすでに試したがエラー解消には至らなかった施策を何度も提示してきました。


### 質問と異なる回答をしてくる
ChatGPT SearchをONにしていると、質問に対する回答がおかしい場面がいくつかありました。例えば、login()にエラーハンドリングを追加してとお願いすると、私が実装していない処理が盛り込まれてでエラーハンドリングがされており、かえって手間がかかる場面がありました。
  
## コーディングで苦戦した点
具体的に苦戦したポイントを下記に記載します。
掲載しているコードの全体像はGithubを確認してください。

### Pytest-asyncioを用いた非同期テストにおけるDBコネクションプーリング起因のイベントループエラー

非同期でPytestを実施する場合、テスト関数ごとのイベントループやコネクションプーリングでエラーが発生する。
	下記のように`poolclass=NullPool`としてコネクションプーリングを使わないようにすれば回避可能です。
```database.py
    # NOTE: AsyncAdaptedQueuePoolではPytest時にイベントループ絡みで失敗するため、開発時はNullPoolにする
    if setting.DEV_MODE:
        # 開発時はコネクションプーリングを保持せずに都度接続＆開放するように設定
        print("Pytest用のDB環境設定")
        engine = create_async_engine(database_url, echo=False, poolclass=NullPool)
    else:
        # 本番環境では非同期でもコネクションプーリングを使いまわすように設定
        engine = create_async_engine(database_url, echo=False, poolclass=AsyncAdaptedQueuePool)
```
  
### 認証済みの場合のみで使用できるAPIにおける依存性注入(特にDB関連)
認証済み(つまり、ログイン済み)の場合のみ使用できるAPIを作成するときに、
 認証情報であるJWTからemailなどのユーザー情報を取得して、そのユーザー情報をもとにDBに対してSelectする時は下記のようにDepends()を定義する。
get_current_user()にもDepends(get_db)が必要でした。

   ```report_controller.py
@router.post("", response_model=ResponseReport)
async def create_report_endpoint(
    report: RequestReport,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
```
```auth_service.py
async def get_current_user(
    db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme),
) -> UserResponse:
```
```database.py
async def get_db() -> AsyncGenerator:
    """非同期データベースセッションを生成するジェネレーター関数。

    Yields:
        AsyncSession: 非同期セッションインスタンス。

    """
    async with AsyncSessionLocal(bind=engine) as session:
        yield session

```

### SQLAlchemy専用ロガーのログレベルが反映されない
app用のログとsqlalchemy用のログを別ファイルをで出力するように実装すると、sqlalchemy用のログ設定ではロガーとハンドラの両方にログレベルを明確に定義することで回避。
```log_config.py
    # SQLAlchemy専用ロガーを設定
    sqlalchemy_logger = logging.getLogger("sqlalchemy")
    sqlalchemy_logger.handlers = []  # 既存ハンドラをクリア
    sqlalchemy_logger.setLevel(logging.WARNING)

    sqlalchemy_file_handler = logging.FileHandler(sqlalchemy_log_file_path, encoding="utf-8")
    sqlalchemy_file_handler.setLevel(logging.WARNING)  # ハンドラのレベルもWARNINGに設定
    sqlalchemy_formatter = logging.Formatter(
        "[%(asctime)s] [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S",
```

# 所感
### ChatGPTについて
ChatGPTを開発で使用する際は、使う側が生成された回答をチェックする能力が重要であり、誤った回答が多いためChatGPTにコーディングを丸投げすることは困難だと実感しました。  

よくあるChatGPTの使い方として「ChatGPTに与える情報を絞る」「具体的な指示を出す」などがありますが、いざ開発で使ってみるとこれらのテクニックでは限界がありました。
ITエンジニアはこれからも勉強しないとダメだなと実感しました。

難しいことをChatGPTに聞く際は、「ChatGPTに解決させるか」「自分で調べるか」の線引きが重要だと実感しました。私の場合は「ある程度自分で調べてChatGPTに聞く」で解決することもありました。

### FastAPIについて
非同期の実装が当たり前となっており、私には新鮮でした。しかし、公式ドキュメントでは、ファイルを分割した場合の実装に関する情報がないため、苦戦することが多かったです。
また、依存性注入や非同期処理など難しいことが多く、今回の学習ではあまりよくわからなかったことも多いです。
また、バージョンによって推奨される書き方が変わるため、比較的新しいフレームワークの実装は注意が必要だと実感しました。

### Dockerについて
ChatGPTでおおよそdocker-compose.ymlやDockerfileが作成できますが、作成済みのFastAPIプロジェクトのDocker化に関する情報が多く、Dockerコンテナで0からFastAPIプロジェクトを作成する方法を見つけることにやや苦労しました。