mcpField
F
fastapi-best-practices

fastapi-best-practices

github.com/ofershap開発1126 回インストール37 週間前に更新
リポジトリ

fastapi-best-practices について

エージェントに FastAPI コードの現行プラクティスを示し、古い書き方を避けるためのスキルです。エンドポイント設計、依存関係、バリデーション、アプリのライフサイクル、構成を扱います。

主な機能

  1. 01処理内容に応じた非同期・同期エンドポイントの選択
  2. 02Depends と Annotated による依存関係管理
  3. 03Pydantic v2 のパターン適用
  4. 04lifespan による起動と終了の管理
  5. 05APIRouter によるルート整理
  6. 06レスポンス検証と設定管理

活用シーン

  1. 01I/O 中心の FastAPI エンドポイントを実装
  2. 02ルートを APIRouter モジュールに再構成
  3. 03非推奨の Pydantic v1 パターンを置き換え
  4. 04FastAPI コードの一般的なアンチパターンを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ofershap/fastapi-best-practices --skill fastapi-best-practices

インストール後、Claude Code で/fastapi-best-practicesを入力して読み込まれたか確認します。

詳細

使用する場面

FastAPI のコードを扱うときに、このスキルを使用してください。最新のベストプラクティスを学び、AI エージェントが古いパターンを使って犯しがちなミスを防ぎます。

重要なルール

1. I/O 待ちのエンドポイントには async def、CPU 負荷の高い処理には def を使う

誤り(エージェントがよく行う例):

@app.get("/users")
def get_users():
    users = db.query(User).all()
    return users

@app.get("/data")
async def get_data():
    result = heavy_computation()
    return result

正しい例:

@app.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User))
    return result.scalars().all()

@app.get("/data")
def get_data():
    return heavy_computation()

理由: FastAPI は非同期エンドポイントをイベントループで実行し、同期エンドポイントをスレッドプールで実行します。ブロッキングを避けるため、I/O(DB、HTTP、ファイル)には async を使います。CPU 負荷の高い処理には def を使います。これを async にすると、イベントループをブロックしてしまいます。

2. 依存性注入には Depends() を使う

誤り(エージェントがよく行う例):

db = get_database()

@app.get("/items")
async def get_items():
    return db.query(Item).all()

正しい例:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/items")
async def get_items(db: Annotated[Session, Depends(get_db)]):
    return db.query(Item).all()

理由: グローバルな DB 接続はリークが発生し、テストが難しく、FastAPI の依存性システムも迂回してしまいます。Depends() を使うと、適切なスコープ管理、クリーンアップ、テスト時のオーバーライドが可能になります。

3. Pydantic v2 のパターンを使う

誤り(エージェントがよく行う例):

from pydantic import validator

class Item(BaseModel):
    name: str
    price: float

    class Config:
        orm_mode = True

    @validator("price")
    def price_positive(cls, v):
        if v <= 0:
            raise ValueError("must be positive")
        return v

正しい例:

from pydantic import BaseModel, field_validator, ConfigDict

class Item(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    name: str
    price: float

    @field_validator("price")
    @classmethod
    def price_positive(cls, v: float) -> float:
        if v <= 0:
            raise ValueError("must be positive")
        return v

理由: Pydantic v1 の validator、Config、orm_mode は非推奨です。field_validator、model_validator、ConfigDict、from_attributes を使ってください。

4. lifespan コンテキストマネージャーを使う

誤り(エージェントがよく行う例):

@app.on_event("startup")
async def startup():
    app.state.db = await create_pool()

@app.on_event("shutdown")
async def shutdown():
    await app.state.db.close()

正しい例:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.db = await create_pool()
    yield
    await app.state.db.close()

app = FastAPI(lifespan=lifespan)

理由: on_event は非推奨です。lifespan コンテキストマネージャーを使うと、リソースの順序を適切に管理しながら、起動と終了の処理を一箇所にまとめられます。

5. fire-and-forget の処理には BackgroundTasks を使う

誤り(エージェントがよく行う例):

@app.post("/send-email")
async def send_email(email: str):
    asyncio.create_task(send_email_async(email))
    return {"status": "queued"}

正しい例:

@app.post("/send-email")
async def send_email(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(send_email_async, email)
    return {"status": "queued"}

理由: asyncio.create_task はリクエストの終了後も実行され続ける可能性があり、シャットダウン時に待機されません。BackgroundTasks はレスポンス送信後に実行され、リクエストのライフサイクルに紐づきます。

6. ルートの整理には APIRouter を使う

誤り(エージェントがよく行う例):

# main.py - 500 lines of routes
@app.get("/users")
@app.get("/users/{id}")
@app.post("/items")
@app.get("/items")

正しい例:

# main.py
app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(items.router, prefix="/items", tags=["items"])

# routers/users.py
router = APIRouter()
@router.get("/")
@router.get("/{id}")

理由: すべてを単一ファイルにまとめたアプリは保守が困難になります。APIRouter を使うと、routers/、models/、services/、dependencies/ のような構成にできます。

7. 出力の検証には response_model を使う

誤り(エージェントがよく行う例):

@app.get("/items/{id}")
async def get_item(id: int):
    item = await db.get(Item, id)
    return {"id": item.id, "name": item.name}

正しい例:

@app.get("/items/{id}", response_model=ItemOut)
async def get_item(id: int, db: Session = Depends(get_db)):
    item = await db.get(Item, id)
    if not item:
        raise HTTPException(status_code=404)
    return item

理由: 生の dict は検証と OpenAPI を迂回してしまいます。response_model を使うと、スキーマの一貫性、シリアル化、ドキュメントが保証されます。

8. ステータスコードには fastapi.status を使う

誤り(エージェントがよく行う例):

raise HTTPException(status_code=404, detail="Not found")
raise HTTPException(status_code=401, detail="Unauthorized")

正しい例:

from fastapi import status

raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found")
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized")

理由: マジックナンバーはエラーの原因になりやすいです。ステータス定数はコードの意図を明確にし、HTTP 仕様にも準拠します。

9. 依存関係には Annotated を使う

誤り(エージェントがよく行う例):

@app.get("/me")
async def read_me(current_user: User = Depends(get_current_user)):
    return current_user

正しい例:

@app.get("/me")
async def read_me(current_user: Annotated[User, Depends(get_current_user)]):
    return current_user

理由: Annotated は FastAPI で推奨されるパターンです。型と依存関係を一箇所にまとめられ、依存関係の再利用にも対応します。

10. 設定には pydantic-settings を使う

誤り(エージェントがよく行う例):

import os
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///db.sqlite")

正しい例:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str = "sqlite:///db.sqlite"
    debug: bool = False
    model_config = {"env_file": ".env"}

settings = Settings()

理由: os.getenv には検証機能も型情報もありません。BaseSettings を使うと、検証、.env の読み込み、型安全性が得られます。

パターン

  • prefix と tags を指定して routers/ にルーターを定義する
  • 共通の依存関係は dependencies.py に置く
  • エラーにはステータス定数を指定した HTTPException を使う
  • 検証(min_length、ge、le)を指定して Path、Query、Body、Header を使う
  • app.add_exception_handler でカスタム例外ハンドラーを登録する
  • ミドルウェアは必要最小限にする。順序が重要(最初に追加したものはリクエスト時に最後に実行されます)

アンチパターン

  • @app.on_event("startup") や @app.on_event("shutdown") を使わない
  • リクエスト単位のバックグラウンド処理に asyncio.create_task を使わない
  • DB、キャッシュ、設定にグローバル変数を使わない
  • Pydantic v1 の @validator や class Config を使わない
  • response_model を指定せずに生の dict を返さない
  • ステータスコードにマジックナンバーを使わない
  • すべてのルートを main.py に記述しない

コメント