1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JetBrains Air+CodexでFastAPIのAPI追加とテストまで進める

1
Posted at

JetBrains Air+CodexでFastAPIのAPI追加とテストまで進める

はじめに

前回は、Windows 11にJetBrains Airを入れ、Codexへ小さなPython修正を依頼するところまで確認しました。

今回は少し範囲を広げます。FastAPIとDocker Composeで動くスターター版を用意し、Air上のCodexへ次の作業をまとめて依頼します。

  • 降雨集計APIの追加
  • Pydanticによる入力チェック
  • サービス関数の修正
  • pytestによる単体テストとAPIテスト
  • Docker Composeのテスト用サービス追加
  • READMEの更新

いきなり実務プロジェクトへ適用するのではなく、まずは小さなAPIで「依頼、Plan確認、実装、テスト、差分確認」の流れを一通り試します。

この記事は2026年7月27日時点のJetBrains Airを前提にしています。

今回作るもの

入力された時間雨量から、合計値、最大値、平均値を返すAPIを作ります。

ChatGPT Image 2026年8月2日 08_47_57.png

追加するAPIは次のとおりです。

POST /api/v1/rainfall/summary

リクエスト例です。

{
  "rainfall_mm": [0.0, 2.5, 10.0, 5.5]
}

レスポンスは次の形にします。

{
  "total_mm": 18.0,
  "max_mm": 10.0,
  "average_mm": 4.5
}

使用する環境

項目 内容
OS Windows 11
開発環境 JetBrains Air
エージェント OpenAI Codex
実行環境 Git Worktree
Python 3.13
Web API FastAPI
コンテナ Docker Desktop / Docker Compose
テスト pytest / FastAPI TestClient

Airでは、タスクごとにエージェント、権限モード、実行環境を選べます。今回は実装前に内容を確認したいためPlanを使い、元の作業フォルダーを直接変更しないようにGit Worktreeを選びます。

配布ファイル

配布用ZIPファイルには、次の2つを入れています。

air_codex_fastapi_docker_r001/
├─ starter/       Codexへ渡す前の状態
├─ completed/     APIとテストを追加した完成版
├─ task_prompts/  Codexへ渡すタスク文
└─ docs/          Qiita記事

Airで試すときはstarterを使用します。completedは、Codexの変更結果を確認するときの比較用です。

1. スターター版を確認する

スターター版には、FastAPIの起動に必要な最低限のファイルだけを置いています。

starter/
├─ app/
│  ├─ services/
│  │  └─ rainfall.py
│  └─ main.py
├─ task_prompts/
│  └─ codex_add_api_and_tests.md
├─ AGENTS.md
├─ compose.yaml
├─ Dockerfile
├─ requirements.txt
└─ README.md

この段階で用意されているAPIは、ヘルスチェックだけです。

@app.get("/health", tags=["system"])
def health_check() -> dict[str, str]:
    """コンテナとAPIプロセスが稼働していることを返す。"""

    return {"status": "ok"}

降雨集計の関数はありますが、空のリストと負の雨量を安全に扱えません。

def summarize_rainfall(rainfall_mm: list[float]) -> dict[str, float]:
    total_mm = sum(rainfall_mm)

    return {
        "total_mm": total_mm,
        "max_mm": max(rainfall_mm),
        "average_mm": total_mm / len(rainfall_mm),
    }

空のリストを渡すとmax()または割り算で例外になります。負の雨量もそのまま計算されます。今回は、この部分もAPI追加と一緒にCodexへ直してもらいます。

2. 先にDocker Composeで起動する

PowerShellでstarterへ移動します。

cd C:\work\air_codex_fastapi_docker_r001\starter

Docker Composeを起動します。

docker compose up --build

ブラウザーで次のURLを開きます。

http://localhost:8000/docs

この段階では、Swagger UIにGET /healthだけが表示されます。

ヘルスチェックはPowerShellからも確認できます。

Invoke-RestMethod http://localhost:8000/health

結果は次のとおりです。

status
------
ok

確認後は停止します。

docker compose down

3. Gitリポジトリを準備する

Git Worktreeを使うため、Airで開く前にGitリポジトリを作成し、初期状態をコミットします。

スターター版には準備用スクリプトを入れています。

.\prepare_repository.ps1

手作業で行う場合は次のコマンドです。

git init
git add .
git commit -m "Create FastAPI starter project"

最後に状態を確認します。

git status

次の表示になれば準備完了です。

nothing to commit, working tree clean

未コミットの変更が残っていると、Codexが変更した部分との区別がつきにくくなります。最初の状態を必ずコミットしてからAirで開きます。

4. Airでスターター版を開く

JetBrains Airを起動し、starterフォルダーを開きます。

C:\work\air_codex_fastapi_docker_r001\starter

今回の設定は次のとおりです。

設定 選択内容
Agent OpenAI Codex
Permission Mode Plan
Run Environment Git Worktree

Planを選ぶと、Codexはいきなりファイルを変更せず、最初に実装計画を作ります。Git Worktreeでは、タスク専用のブランチと作業フォルダーが作られるため、現在の作業コピーは変更されません。

Airの公式ドキュメントでも、Planモードは実装前の計画確認、Git Worktreeは元の作業コピーから変更を分離する用途として説明されています。

5. AGENTS.mdで作業ルールを固定する

スターター版にはAGENTS.mdを置いています。

# 開発ルール

- Python 3.13で動作するコードにする。
- ソースコードはUTF-8で保存する。
- 公開関数と公開クラスには日本語のdocstringを付ける。
- コメントは判断理由や注意点が必要な箇所に記述する。
- APIのURLとレスポンス項目は依頼内容を優先する。
- 実装後はpytestを実行する。
- Docker Composeによるテストも実行する。
- 今回の目的に関係しないリファクタリングは行わない。
- 不要になったコードは残さず削除する。

タスク本文だけでも指示できますが、毎回守ってほしいルールはAGENTS.mdへ分けた方が管理しやすくなります。

6. Codexへ渡すタスク

AirのChatへ、次のタスクを貼り付けます。

このFastAPIプロジェクトに、降雨集計APIと自動テストを追加してください。

現在の状態:
- GET /health は実装済みです。
- app/services/rainfall.py に summarize_rainfall() があります。
- 降雨集計用のAPIとテストはまだありません。
- Docker ComposeではAPIサービスだけを起動できます。

追加するAPI:
- メソッド: POST
- URL: /api/v1/rainfall/summary

リクエスト例:
{
  "rainfall_mm": [0.0, 2.5, 10.0, 5.5]
}

レスポンス例:
{
  "total_mm": 18.0,
  "max_mm": 10.0,
  "average_mm": 4.5
}

完了条件:
1. POST /api/v1/rainfall/summary を追加する。
2. 入出力にはPydanticモデルを使う。
3. rainfall_mm が空の場合はHTTP 422を返す。
4. 負の雨量を含む場合はHTTP 422を返す。
5. summarize_rainfall([]) は理由の分かるValueError系例外を送出する。
6. summarize_rainfall() に負の雨量を渡した場合も理由の分かる例外を送出する。
7. 正常な入力では既存の辞書キーを変えずに集計結果を返す。
8. pytestでサービス単体テストとAPI結合テストを追加する。
9. GET /health、正常な降雨集計、空リスト、負の雨量をテストする。
10. Dockerfileにテスト用ステージを追加する。
11. Composeにテスト用サービスを追加する。
12. 次のコマンドでテストが成功するようにする。

   docker compose --profile test run --rm --build test

13. READMEへAPIの呼び出し方とテスト方法を追記する。

変更時の条件:
- Python 3.13を使用する。
- GET /health のURLとレスポンスは変更しない。
- summarize_rainfall の関数名と引数名を変更しない。
- total_mm、max_mm、average_mm のキー名を変更しない。
- APIのバージョンは /api/v1 とする。
- 公開関数と公開クラスには日本語のdocstringを付ける。
- コメントは判断理由が必要な箇所にだけ追加する。
- 今回の目的に関係しないリファクタリングは行わない。
- 実装後にローカルpytestとDocker Composeのテストを実行し、結果を報告する。

まずPlanモードで、変更対象ファイル、実装手順、確認方法を整理してください。
Planを承認する前にファイルを変更しないでください。

同じ内容をtask_prompts/codex_add_api_and_tests.mdにも保存しています。

Airでは、入力欄で@を使うとファイルやフォルダーをコンテキストへ追加できます。今回は次の項目を添付しておくと調査範囲がぶれにくくなります。

@app
@compose.yaml
@Dockerfile
@README.md
@AGENTS.md

7. Planで確認する内容

タスクを送ると、Codexが変更対象と実装手順を整理します。

想定されるPlanは次のような内容です。

1. 既存のFastAPI構成と降雨集計関数を確認する
2. リクエスト・レスポンス用のPydanticモデルを作る
3. 降雨集計用のAPIRouterを追加する
4. main.pyへルーターを登録する
5. サービス関数へ入力チェックを追加する
6. pytestでサービス単体テストを作る
7. TestClientを使ったAPIテストを作る
8. Dockerfileへテストステージを追加する
9. compose.yamlへテストサービスを追加する
10. READMEを更新する
11. ローカルとDocker Composeでテストする

Planでは、次の点を確認します。

  • GET /healthを変更しない計画になっているか
  • APIのURLが/api/v1/rainfall/summaryになっているか
  • 入力チェックをAPIだけでなくサービス側にも追加するか
  • 単体テストとAPIテストの両方が含まれているか
  • Dockerfileの実行用イメージにテスト依存を混ぜない構成か
  • 関係のないファイルを変更しようとしていないか

問題がなければ実装へ進めます。

計画に不要な変更が含まれていた場合は、この段階で直します。例えば、データベースやログ機能まで追加しようとしていたら、次のように返します。

今回は降雨集計APIとテスト追加だけを対象にしてください。
データベース、認証、ログ基盤の追加は行わないでください。

8. Codexが追加する構成

完成後の構成は次の形になります。

completed/
├─ app/
│  ├─ api/
│  │  └─ routes/
│  │     └─ rainfall.py
│  ├─ schemas/
│  │  └─ rainfall.py
│  ├─ services/
│  │  └─ rainfall.py
│  └─ main.py
├─ tests/
│  ├─ test_api.py
│  └─ test_rainfall_service.py
├─ AGENTS.md
├─ compose.yaml
├─ Dockerfile
├─ requirements.txt
├─ requirements-dev.txt
└─ README.md

API、入出力モデル、業務ロジックを分けています。小さなサンプルでは1ファイルにもできますが、次の機能追加を考えると、最初から役割を分けた方が追いやすくなります。

9. 入力チェックをPydanticへ追加する

APIの入力モデルでは、リストが1件以上であることと、各雨量が0以上であることを定義します。

from typing import Annotated

from pydantic import BaseModel, Field

NonNegativeRainfall = Annotated[
    float,
    Field(ge=0.0, description="0以上の時間雨量(mm)"),
]


class RainfallSummaryRequest(BaseModel):
    """降雨集計APIのリクエスト。"""

    rainfall_mm: Annotated[
        list[NonNegativeRainfall],
        Field(min_length=1, description="集計対象の時間雨量(mm)"),
    ]

この定義により、空のリストや負の値を送った場合、FastAPIがHTTP 422を返します。

入力チェックをルーター内のif文だけで書くより、Swagger UIにも条件が反映されるため、APIの仕様が分かりやすくなります。

10. サービス側にも入力チェックを残す

Pydanticで検証していても、summarize_rainfall()がAPI以外から呼ばれる可能性があります。

そのため、サービス関数側でも空リストと負の値を確認します。

class RainfallValidationError(ValueError):
    """降雨量の入力値が集計条件を満たさない場合に送出する例外。"""


def summarize_rainfall(rainfall_mm: list[float]) -> dict[str, float]:
    """雨量の合計値、最大値、平均値を計算する。"""

    if not rainfall_mm:
        raise RainfallValidationError(
            "rainfall_mmには1件以上の雨量を指定してください。"
        )

    if any(value < 0.0 for value in rainfall_mm):
        raise RainfallValidationError(
            "rainfall_mmには0以上の雨量を指定してください。"
        )

    total_mm = sum(rainfall_mm)

    return {
        "total_mm": total_mm,
        "max_mm": max(rainfall_mm),
        "average_mm": total_mm / len(rainfall_mm),
    }

APIからの入力はPydantic、業務ロジック単体ではサービス関数が守る形です。

11. APIルーターを追加する

エンドポイントはAPIRouterへ分けます。

from fastapi import APIRouter

from app.schemas.rainfall import (
    RainfallSummaryRequest,
    RainfallSummaryResponse,
)
from app.services.rainfall import summarize_rainfall

router = APIRouter(prefix="/api/v1/rainfall", tags=["rainfall"])


@router.post("/summary", response_model=RainfallSummaryResponse)
def create_rainfall_summary(
    request: RainfallSummaryRequest,
) -> RainfallSummaryResponse:
    """受け取った時間雨量から合計値、最大値、平均値を返す。"""

    summary = summarize_rainfall(request.rainfall_mm)
    return RainfallSummaryResponse(**summary)

main.pyではルーターを登録します。

from app.api.routes.rainfall import router as rainfall_router

app.include_router(rainfall_router)

ヘルスチェックはそのまま残します。

12. pytestでサービスとAPIを分けて確認する

テストは2種類に分けます。

test_rainfall_service.py
  └─ 集計関数だけを確認する

test_api.py
  └─ HTTPリクエストとレスポンスを確認する

サービス単体テストでは、正常値、空リスト、負の雨量を確認します。

def test_summarize_rainfall_returns_expected_values() -> None:
    result = summarize_rainfall([0.0, 2.5, 10.0, 5.5])

    assert result == {
        "total_mm": 18.0,
        "max_mm": 10.0,
        "average_mm": 4.5,
    }

APIテストではFastAPIのTestClientを使います。

def test_create_rainfall_summary() -> None:
    response = client.post(
        "/api/v1/rainfall/summary",
        json={"rainfall_mm": [0.0, 2.5, 10.0, 5.5]},
    )

    assert response.status_code == 200
    assert response.json() == {
        "total_mm": 18.0,
        "max_mm": 10.0,
        "average_mm": 4.5,
    }

空リストと負の雨量については、HTTP 422が返ることを確認します。

13. Dockerfileへテスト用ステージを追加する

完成版のDockerfileは、実行用とテスト用を分けています。

FROM python:3.13-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /workspace

COPY requirements.txt ./
RUN python -m pip install --upgrade pip \
    && python -m pip install -r requirements.txt


FROM base AS runtime

COPY app ./app

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]


FROM base AS test

COPY requirements-dev.txt ./
RUN python -m pip install -r requirements-dev.txt

COPY app ./app
COPY tests ./tests

CMD ["python", "-m", "pytest", "-q"]

実行用ステージにはpytestを入れず、テスト用ステージだけに開発用依存関係を追加しています。

14. Composeへテストサービスを追加する

compose.yamlには、通常起動するapiと、必要なときだけ使うtestを定義します。

services:
  api:
    build:
      context: .
      target: runtime
    ports:
      - "8000:8000"

  test:
    profiles:
      - test
    build:
      context: .
      target: test

テストサービスはtestプロファイルへ入れています。通常のdocker compose upではAPIだけが起動します。

15. Codexにテストまで実行させる

実装後、Codexには次の2種類のテストを実行させます。

ローカルpytestです。

python -m pip install -r requirements-dev.txt
python -m pytest -q

Docker Composeによるテストです。

docker compose --profile test run --rm --build test

配布している完成版をローカルpytestで確認した結果は次のとおりです。

.......                                                                  [100%]
7 passed

Codexの最終回答では、単に「テストしました」だけではなく、実行コマンド、成功件数、未確認事項を報告させます。

例えば次の形です。

実行したコマンド:
- python -m pytest -q
- docker compose --profile test run --rm --build test

結果:
- サービス単体テスト: 成功
- APIテスト: 成功
- 合計7件: 成功
- Dockerイメージのビルド: 成功

コマンドの実行結果が表示されていない場合は、追加で確認します。

実行したテストコマンドと終了コードを示してください。
未実行の確認項目があれば、その理由も書いてください。

16. Changes画面で差分を確認する

Codexの作業が終わったら、AirのChanges画面を開きます。

確認する点は次のとおりです。

API

  • URLが/api/v1/rainfall/summaryになっているか
  • レスポンスの3項目が変わっていないか
  • GET /healthが残っているか

入力チェック

  • 空リストがHTTP 422になるか
  • 負の雨量がHTTP 422になるか
  • サービス関数を直接呼んだ場合も例外になるか

テスト

  • 正常系だけでなく異常系もあるか
  • HTTPステータスだけでなくレスポンス内容も確認しているか
  • 実際のAPIルーターを通るテストになっているか

Docker

  • 実行用イメージへpytestを入れていないか
  • docker compose upでテストサービスまで起動しないか
  • テストコマンドがREADMEに書かれているか

不要な変更があれば、該当行にコメントを付けてCodexへ返します。

17. 変更をローカルへ反映する

差分に問題がなければApply Locallyを実行します。

Git Worktree側の変更が、現在の作業フォルダーへ未コミットの変更としてコピーされます。

PowerShellで状態を確認します。

git status

想定される変更は次のとおりです。

modified:   Dockerfile
modified:   README.md
modified:   app/main.py
modified:   app/services/rainfall.py
modified:   compose.yaml
modified:   requirements-dev.txt
new file:   app/api/routes/rainfall.py
new file:   app/schemas/rainfall.py
new file:   tests/test_api.py
new file:   tests/test_rainfall_service.py

ローカルへ反映した後、もう一度テストします。

docker compose --profile test run --rm --build test

問題がなければコミットします。

git add .
git commit -m "Add rainfall summary API and tests"

18. Swagger UIから動作確認する

APIを起動します。

docker compose up --build

ブラウザーでSwagger UIを開きます。

http://localhost:8000/docs

POST /api/v1/rainfall/summaryを開き、次のJSONを入力します。

{
  "rainfall_mm": [0.0, 2.5, 10.0, 5.5]
}

レスポンスが次の内容になれば正常です。

{
  "total_mm": 18.0,
  "max_mm": 10.0,
  "average_mm": 4.5
}

空リストも確認します。

{
  "rainfall_mm": []
}

この場合はHTTP 422になります。

負の雨量も同様です。

{
  "rainfall_mm": [1.0, -0.1, 2.0]
}

こちらもHTTP 422になれば入力チェックが動いています。

19. PowerShellからAPIを呼び出す

PowerShellでは次のように実行できます。

$body = @{
    rainfall_mm = @(0.0, 2.5, 10.0, 5.5)
} | ConvertTo-Json

Invoke-RestMethod `
    -Method Post `
    -Uri "http://localhost:8000/api/v1/rainfall/summary" `
    -ContentType "application/json" `
    -Body $body

結果は次の形で表示されます。

total_mm max_mm average_mm
-------- ------ ----------
    18.0   10.0        4.5

まとめ

今回は、FastAPIとDocker Composeで作った小さなスターター版をAirで開き、CodexへAPI追加からテストまでまとめて依頼する流れを整理しました。

作業の順番は次のとおりです。

作業手順のまとめ

  1. スターター版を起動
  2. Gitへ初期コミット
  3. AirでCodex・Plan・Git Worktreeを選択
  4. API仕様と完了条件を渡す
  5. Planを確認
  6. CodexがAPIとテストを追加
  7. ローカルpytestとDockerテストを実行
  8. Changes画面で差分を確認
  9. Apply Locally
  10. 手元でもう一度テスト

Codexへ依頼するときは、実装内容だけでなく、変更してはいけない部分とテストコマンドまで書いておくことが大切です。

今回のサンプルでは、GET /health、関数名、レスポンス項目を固定しました。このように既存仕様を明記しておくと、必要以上に変更されるのを抑えられます。

小さなAPIで流れを確認できたら、次はDuckDBへ計算結果を保存する構成や、OpenLayersからAPIを呼び出す構成へ広げられます。

参考資料


1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?