👻

FastAPIに入門する(ルーティング、バリデーション、テストとか)

に公開

前回 Hello Worldしたので、次はfast apiの基本的な機能を触ってみました。

1. 改めて、Hello Worldの実装

再掲になりますが、Hello Worldです。

1.1 基本的なAPIエンドポイント(main.py)

from fastapi import FastAPI
from app.routes.v1 import root as v1_root

app = FastAPI()

app.include_router(v1_root.router, prefix="/v1", tags=["v1"])

@app.get("/")
async def read_main():
    return {"msg": "Hello World"}

1.2 テストの実装

FastAPIではTestClientを使って、簡単にAPIのテストができます。以下は、トップエンドポイントのテスト例です。

from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}

2. ディレクトリ構成とファイルの中身

ディレクトリ構成です。テスト含め3ファイルのみです。

app/
  routes/
    v1/
      root.py  # APIルーターが定義されているファイル
  main.py  # アプリケーションのエントリーポイント
tests/
  test_main.py  # テストコード

2.1 main.py

ルーティングをまとめるために、APIエンドポイントをroot.pyに記述し、app.include_routerで読み込みます。

# main.py
from fastapi import FastAPI  
from app.routes.v1 import root as v1_root  
  
app = FastAPI()  
  
app.include_router(v1_root.router, prefix="/v1", tags=["v1"])  
  
@app.get("/")  
async def read_main():  
    return {"msg": "Hello World"}

2.2 root.py

具体的なルーティング、リクエストボディのバリデーション、そしてレスポンスの定義を行います。ファイルは分けたほうがよいですが、機能の確認なのでヨシ。

# root.py
from fastapi import APIRouter  
from pydantic import BaseModel, Field  
from enum import Enum  
  
from starlette.responses import JSONResponse  
from starlette.status import HTTP_200_OK  
  
router = APIRouter()  
  
  
class CategoryEnum(str, Enum):  
    tech = "tech"  
    life = "life"  
    other = "other"  
  
  
class ExampleRequest(BaseModel):  
    example_name: str = Field(..., min_length=3, max_length=50, description="3文字以上50文字以下の名前")  
    description: str = Field(..., max_length=200, description="最大200文字までの説明")  
    category: CategoryEnum = Field(..., description="カテゴリ(必須)")  
  
  
class ExampleResponse(BaseModel):  
    example_name: str  
    description: str  
    category: CategoryEnum  
    message: str  
  
  
@router.post(  
    "/examples/",  
    status_code=HTTP_200_OK,  
    response_model=ExampleResponse,  
    summary="サンプルデータの作成",  
    description="リクエストされたデータをバリデーションし、レスポンスとして返します。"  
)  
async def create_example(data: ExampleRequest) -> JSONResponse:  
    return JSONResponse(  
        {  
            "example_name": data.example_name,  
            "description": data.description,  
            "category": data.category,  
            "message": "Example received and validated successfully"  
        }  
    )

3. ルーティング

FastAPIでは、APIRouterを使ってエンドポイントをグループ化することができます。
例えば、上記のroot.pyでは、/v1というプレフィックスを付けることで、バージョン管理が容易になります。

# main.py
from fastapi import FastAPI
from app.routes.v1 import root as v1_root

app = FastAPI()

app.include_router(v1_root.router, prefix="/v1", tags=["v1"])

4. バリデーション

FastAPIは、Pydanticを利用してリクエストボディのデータバリデーションを自動で行います。

4.1 Pydanticによるバリデーションの仕組み

ExampleRequestクラスでは、以下のようなバリデーションを定義しています。

  • example_name
    • 最小3文字、最大50文字の文字列
    • descriptionを書くと、生成されるドキュメントに書いてくれます。動作には影響ないです。
  • description
    • 最大200文字までの文字列
  • category
    • 定義されたEnumの値(“tech”、“life”、“other”)のいずれかである必要がある
      リクエストがこれらの制約に違反した場合、FastAPIは自動的にエラーレスポンス(HTTP 422 Unprocessable Entity)を返してくれます。

4.2 実際のリクエストとレスポンスの例

例えば、以下のような正しいJSONデータを送信すると、期待したレスポンスが返ります。

リクエスト例:

{
  "example_name": "FastAPI入門",
  "description": "FastAPIの基本を学ぶサンプル",
  "category": "tech"
}

レスポンス例:

{
  "example_name": "FastAPI入門",
  "description": "FastAPIの基本を学ぶサンプル",
  "category": "tech",
  "message": "Example received and validated successfully"
}

エラー例(HTTP 422):

{
  "detail": [
    {
      "loc": [
        "body",
        "example_name"
      ],
      "msg": "ensure this value has at least 3 characters",
      "type": "value_error.any_str.min_length",
      "ctx": {
        "limit_value": 3
      }
    }
  ]
}

6. まとめ

fastapiは…

  • ルーティング:グルーピングして分離できる。
  • バリデーション:デフォルトで入ってる。
  • テスト:pytestを入れればできる

ORMは無いみたい。あと、依存注入は微妙だった。たぶん関数型のパラダイムをよく分かっていないからかもしれない。

Discussion