早引き FastAPI

2021/03/31に公開

Python で API サーバー立てるのに FastAPI が良さそうなので、ドキュメントをナナメ読みした。
チュートリアルだけでもボリューミーだったので、辞書的に知りたい情報にアクセスできるようにまとめる。

記事執筆時の 2021-03-30 時点で FastAPI のメジャーバージョンは 0 なので、いずれこの記事は陳腐化する可能性が高い。あくまで執筆時点での整理なのでご了承を。

FastAPI の特徴

ここにまとまっているが、

https://fastapi.tiangolo.com/

https://fastapi.tiangolo.com/features/

  • Starlette という軽量 Web フレームワーク
  • Pydantic という型アノテーションによるバリデーション/シリアライゼーションのライブラリ

すばやく FastAPI を使っていく意味では、上記の2つのライブラリに大きく依存しているのがポイント。

とりあえず使う

$ pip install fastapi
$ pip install uvicorn[standard]
main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}
$ uvicorn main:app --reload

https://fastapi.tiangolo.com/tutorial/

https://fastapi.tiangolo.com/tutorial/first-steps/

パラメータ

パスオペレーション関数(path operation function)の引数に指定できるパラメータのこと。

ここではどのパラメータがどの関数なのか理解やすくするために Path(...) のように、追加の引数を何も指定せずにパラメータを指定している。
追加の引数が不要な場合は Path などの関数は省略できるので、 FastAPI のドキュメントのように省略したスタイルで記載したほうが実際はわかりやすい。

自分が見た限りだと各パラメータの追加の引数が一覧で整理されたページはなかったので、現状はソースコードを確認するか、コントリビュートしよう。

https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

Path

パスパラメータを設定するには Path を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
追加の引数が不要な場合は Path を使わずに直接初期値を渡すことができる。

main.py
from typing import Optional


from fastapi import FastAPI, Path


app = FastAPI()


@app.get("/items/{item_id}/comments/{comment_id}")
async def read_comment(
    item_id: int = Path(...),
    comment_id: Optional[int] = None,
):
    if comment_id is None:
        return {"item_id": item_id}
    return {"item_id": item_id, "comment_id": comment_id}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

パスパラメータの概要
https://fastapi.tiangolo.com/tutorial/path-params/

パスパラメータの追加の引数の概要
https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/

Query

クエリパラメータを設定するには Query を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
追加の引数が不要な場合は Query を使わずに直接初期値を渡すことができる。

main.py
from typing import Optional


from fastapi import FastAPI, Query


app = FastAPI()


@app.get("/items")
async def read_items(
    name: str = Query(...),
    title: Optional[int] = None,
):
    if title is None:
        return {"name": name}
    return {"name": name, "title": title}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

クエリパラメータの概要
https://fastapi.tiangolo.com/tutorial/query-params/

クエリパラメータの追加の引数の概要
https://fastapi.tiangolo.com/tutorial/query-params-str-validations/

Body

ボディパラメータを設定するには Body を使用する。
ボディの型定義とバリデーションするために BaseModel を継承したクラスを定義する。
BaseModel を継承したクラスで各フィールドを定義するために Field を使用する。

BaseModelFiledpydantic から import する点に注意する。

BaseModel

ボディの型定義とバリデーションするために BaseModel を継承したクラスを定義する。
設定は内部クラス Config として定義できる。不要な場合は省略できる。

from typing import Optional

from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None

    class Config:
        title = "Item"

BaseModel の概要

https://fastapi.tiangolo.com/tutorial/body/

BaseModel の設定項目

https://pydantic-docs.helpmanual.io/usage/model_config/

Field

BaseModel を継承したクラスにフィールドを設定するには Field を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
追加の引数が不要な場合は Field を使わずに直接初期値を渡すことができる。

from typing import Optional

from pydantic import BaseModel, Field


class Item(BaseModel):
    name: str = Field(...)
    description: Optional[str] = None

Field の概要
https://fastapi.tiangolo.com/tutorial/body-fields/

Field の対応する型
https://pydantic-docs.helpmanual.io/usage/types/

Field の追加の引数
https://pydantic-docs.helpmanual.io/usage/schema/

Body

ボディパラメータを設定するには Body を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
追加の引数が不要な場合は Body を使わずに直接初期値を渡すことができる。

複数の Body を指定する場合は、受け取るボディの形式が変わるので注意する。

main.py
from typing import Optional


from fastapi import FastAPI, Body
from pydantic import BaseModel


app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str]


class User(BaseModel):
    name: str
    description: Optional[str]


@app.post("/items")
async def create_item(item: Item = Body(...), user: Optional[User] = None):
    if user is None:
        return {"item": item}
    return {"item": item, "user": user}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

ボディパラメータの概要
https://fastapi.tiangolo.com/tutorial/body/

ネストしたボディパラメータの概要
https://fastapi.tiangolo.com/tutorial/body-nested-models/

複数のボディパラメータを指定するときの概要
https://fastapi.tiangolo.com/tutorial/body-multiple-params/

ボディパラメータにメタデータを付与するときの概要
https://fastapi.tiangolo.com/tutorial/schema-extra-example/

クッキーパラメータを設定するには Cookie を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
クエリパラメータとして認識されてしまうため、追加の引数が不要な場合でも Cookie を使わなければならない。

main.py
from typing import Optional


from fastapi import Cookie, FastAPI


app = FastAPI()


@app.get("/items")
async def read_items(
    name: str = Cookie(...),
    title: Optional[int] = Cookie(None),
):
    if title is None:
        return {"name": name}
    return {"name": name, "title": title}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

クッキーパラメータの概要
https://fastapi.tiangolo.com/tutorial/cookie-params/

ヘッダーパラメータを設定するには Header を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
クエリパラメータとして認識されてしまうため、追加の引数が不要な場合でも Header を使わなければならない。

重複したヘッダーは型アノテーションとして List を指定することで、配列で受け取ることができる。

main.py
from typing import Optional


from fastapi import FastAPI, Header


app = FastAPI()


@app.get("/items")
async def read_items(
    x_tokens: List[str] = Header(...),
    user_agent: Optional[str] = Header(None),
):
    if user_agent is None:
        return {"x_tokens": x_tokens}
    return {"x_tokens": x_tokens, "user_agent": user_agent}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

ヘッダーパラメータの概要
https://fastapi.tiangolo.com/tutorial/header-params/

Form

フォームパラメータを設定するには Form を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
クエリパラメータもしくはボディパラメータとして認識されてしまうため、追加の引数が不要な場合でも Form を使わなければならない。

フォームパラメータを使用するには python-multipart をインストールする必要がある。

$ pip install python-multipart
main.py
from typing import Optional


from fastapi import FastAPI, Form


app = FastAPI()


@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

フォームパラメータの概要
https://fastapi.tiangolo.com/tutorial/request-forms/

File

ファイルアップロードに対応するには File を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
クエリパラメータもしくはボディパラメータとして認識されてしまうため、追加の引数が不要な場合でも File を使わなければならない。

型アノテーションとして bytes もしくは UploadFile を指定できる。
bytes はそのまま bytes として値を受け取り、 UploadFile を指定すると UploadFile として値を受け取る。
UploadFile だとファイル名といったメタ情報にアクセスできるなどの利点があるので、一般的には UploadFile の使用が推奨される。

File を使用するには python-multipart をインストールする必要がある。

$ pip install python-multipart
main.py
from fastapi import FastAPI, File, UploadFile


app = FastAPI()


@app.post("/file/")
async def create_file(file: bytes = File(...)):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

ファイルパラメータの概要
https://fastapi.tiangolo.com/tutorial/request-files/

Depends

Dependency Injection を導入するには Depends を使用する。
引数に呼び出し可能なオブジェクト(関数やクラス)を指定する。

引数に指定する呼び出し可能なオブジェクトは、パスパラメータ関数の引数指定できるものを引数として取る。

Depends は上書き可能なので、テスト時だけクラスを差し替えるといったことができる。

main.py
from abc import ABC, abstractmethod

from fastapi import Depends, FastAPI


app = FastAPI()


class SlackClientInterface(ABC):
    @abstractmethod
    def send_message(self, message: str) -> None:
        raise NotImplementedError


class SlackClient():
    def send_message(self, message: str) -> None
        print(f"send message: {message}")


async def get_slack_client():
    return SlackClient()


@app.post("/send")
async def send_message(message: str, slack_client: SlackClientInterface = Depends(get_slack_client)):
    slack_client.send_message(message)
    return {"message": message}


@app.post("/login")
async def login(
    username: str = Form(...),
    password: str = Form(...),
    slack_client: SlackClientInterface = Depends(get_slack_client),
):
    slack_client(f"Welcome {username}!")
    return {"username": username}
test.py
from fastapi import Depends, FastAPI

from main import app, SlackClientInterface


class SlackClientMock():
    def send_message(self, message: str) -> None
        print(f"send mock message: {message}")


async def get_slack_client_for_test():
    return SlackClientMock()


app.dependency_overrides[get_slack_client] = get_slack_client_for_test
client = TestClient(app)


def test_send_message():
    response = client.get("/send?message=hello")
    assert response.status_code == 200
    assert response.json()["message"] == "hello"

追加の引数の一覧
https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py

Dependency Injection の概要
https://fastapi.tiangolo.com/tutorial/dependencies/

BackgroundTasks

レスポンスを返したあとに実行するバックグラウンドタスクを定義するには BackgroundTasks を使用する。
第一引数には初期値を入れる。
オプションの場合は None を、必須かつ初期値を設定しない場合は ... を指定する。
クエリパラメータもしくはボディパラメータとして認識されてしまうため、追加の引数が不要な場合でも File を使わなければならない。

File を使用するには python-multipart をインストールする必要がある。

$ pip install python-multipart
main.py
from fastapi import FastAPI, File, UploadFile


app = FastAPI()


@app.post("/file/")
async def create_file(file: bytes = File(...)):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

バックグラウンドタスクの概要
https://fastapi.tiangolo.com/tutorial/background-tasks/

https://www.starlette.io/background/

パスオペレーション設定

体力切れのため、項目だけ羅列する。

https://fastapi.tiangolo.com/tutorial/path-operation-configuration/

https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/

response_model

https://fastapi.tiangolo.com/tutorial/response-model/

status_code

https://fastapi.tiangolo.com/tutorial/response-status-code/

tags

https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags

dependencies

https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/#dependencies-for-groups-of-path-operations

https://fastapi.tiangolo.com/tutorial/bigger-applications/

Discussion