🐍

FastAPI ①

2023/12/02に公開

PythonのWebフレームワークといえばDjangoやflaskが有名だが、最近FastAPIというフレームワークをよく聞くので少し触ってみようと思う。

Fast API とは

名前にもある通り、パフォーマンスの良さを売りにした、Python3.7以上をターゲットにしたWeb APIを開発するためのフレームワーク。

元々PythonにはWSGI(ウィスキー) という、WebサーバーとWebアプリケーションの間のインターフェイスを定義した仕様が存在したが、非同期処理にも対応したASGIという仕様が2019年ごろに策定され、その仕様に従ったWebサーバー実装であるUvicornと、ASGI対応のWebフレームワークのStarletteの上に乗る形でさまざまな機能を追加してフレームワークとして提供しているのがFast APIだ。

また、型ヒントとデータバリデーションを強化するためにPydanticというライブラリを利用している。

早速インストールしてみる。

Install

pip install fastapi

また、ASGIサーバーとしてここではUvicornを利用する[1]

pip install "uvicorn[standard]"

では簡単な例としてHello, worldを返却するエンドポイントを実装してみる。

Hello, world

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def say_hello():
    return {"Hello": "World"}

実行:

uvicorn main:app --reload

ポート番号8000番でサーバーが立ち上がるので、リクエストを送信してみる:

curl localhost:8000

以下のようなレスポンスが返ってくればOK。

{"Hello":"World"}

ここで面白いのは、Swagger UIが自動で提供されることだ。ブラウザからlocalhost:8000/docsにアクセスしてみる:

加えて、localhost:8000/redocにアクセスすると、ReDocによるドキュメント生成がホスティングされている。

ReDocについては初めて知ったが、Swagger UIよりもデザインがシュッとしている印象。

ただ、両方はいらない、あるいは本番環境ではオフにしたい機能なため、以下のようにすることで選択的にオフにできる。

from fastapi import FastAPI
app = FastAPI(docs_url=None, redoc_url=None)

コードを書くこと=APIドキュメント作成になるのはなかなかいい開発体験になっている一方で、サービスに直接関係ないコードはオプションでの追加にして欲しいと思ったりもする。

ではもう少し実践的な例として簡単なTODOアプリの機能のためのエンドポイントを実装してみる。
簡単のためデータはインメモリに保存するが、おいおいDBに保存していこうと思う。

TODO機能エンドポイント

実装してみるのは以下のエンドポイント

  • POST /todos — 新たなTODOを作成するエンドポイント
  • GET /todos — 作成したTODOの一覧を取得するエンドポイント
  • GET /todos/:id — TODOの詳細を取得するエンドポイント

ではまず、TODOを作成するのエンドポイントから。

POST /todos

TODOを表すデータモデルとしてTodoを定義、TODOの保存先として辞書を作成しておく。

from typing import Dict
import uuid
from pydantic import BaseModel

...

class Todo(BaseModel):
    id: uuid.UUID
    name: str

todos: Dict[uuid.UUID, Todo] = dict()

リクエストボディとしてTODOのタイトルを受け取り、先に作成しておいたdictionaryに保存する。

...

class CreateTodoRequest(BaseModel):
    name: str

@app.post("/todos")
def create_todo(request: CreateTodoRequest):
    id = uuid.uuid4()
    todo = Todo(id=id, name=request.name)
    todos[id] = todo
    return todo

以下のようにリクエスト、レスポンスが返ってくればOK。

curl -X POST localhost:8000/todos -d '{"name":"clean the house"}' -H 'Content-Type: application/json'
{"id":"db7f7c0a-f0c5-4771-a677-b7928f6e48d9","name":"clean the house"}

http://localhost:8000/redocのドキュメントも同時に更新されているので確認してみよう。

POST /todos を追加した際にredocが更新されている

では次に作成したTODOを一覧で取得してみる。

GET /todos

@app.get("/todos")
def list_todos():
    return list(todos.values())

:::message warning
Fast API ではPythonファイルを保存する度にリロードされるため、dictionaryに保存された内容が消えます。面倒ですが今は毎回POST /todosを読んで新しいTODOを作成しましょう。
:::

では確認してみる。

curl -X POST localhost:8000/todos -d '{"name":"clean the house"}' -H 'Content-Type: application/json'
curl -X POST localhost:8000/todos -d '{"name":"take the rubbish out"}' -H 'Content-Type: application/json'
{"id":"74f6a63d-8bbc-4625-9d10-8d595e7ec411","name":"take the rubbish out"}
curl -X POST localhost:8000/todos -d '{"name":"wash the dishes"}' -H 'Content-Type: application/json'
{"id":"6aad1e7c-5af0-4be5-a058-cf810b51ae24","name":"wash the dishes"}
curl localhost:8000/todos
[{"id":"039cacf7-1306-40ee-a229-de1dead0a66c","name":"clean the house"},{"id":"c29abbce-d4df-4a6a-8bd0-476c0f296a58","name":"clean the house"},{"id":"74f6a63d-8bbc-4625-9d10-8d595e7ec411","name":"take the rubbish out"},{"id":"6aad1e7c-5af0-4be5-a058-cf810b51ae24","name":"wash the dishes"}]

GET /todos/:id

では最後にTODOの詳細を取得するエンドポイントを実装する。

@app.get("/todos/{id}")
def get_todo(id: uuid.UUID) -> Todo:
    print(id)
    return todos[id]

リクエストして動作を確かめてみる。

curl -X POST localhost:8000/todos -d '{"name":"wash the dishes"}' -H 'Content-Type: application/json'
{"id":"07e1fb53-8b5a-4a27-aa63-658bc714ffc5","name":"wash the dishes"}
curl localhost:8000/todos/07e1fb53-8b5a-4a27-aa63-658bc714ffc5
{"id":"07e1fb53-8b5a-4a27-aa63-658bc714ffc5","name":"wash the dishes"}

まとめ

というわけでFast APIを簡単に触ってみた。

ここまでとても書きやすく、学習コストがとても低い印象ではあるが、この先より複雑な要件を追加していくにつれてどのような印象になっていくか、楽しみ。

ドキュメントが自動で更新されるのは他のフレームワークであっても用意するのが難しいことではないかもしれないが、実際にとても便利なので勝手についてくるのはありがたいところ。

今回のコードは KentaKudo/fastapi-example として公開しているので、コード全体を確認したい方はご覧ください。

では、また。

脚注
  1. ここではUvicornのインストール時に[standard]をつけることで、Cythonベースで依存関係が最小のパッケージを指定している。 ↩︎

Discussion