読み終えるまでの目安: 20分
目次
概要
最近、Pythonの REST APIフレームワークである FastAPIを触る機会があった
その開発体験がとても快適だったので、ここで紹介してみようと思う
初めて触れる技術でものをつくるにあたっては、プロセスの随所につまずきポイントがあることと思う
事前に調査しているときやお試しで動作させるとき、本番用の実装をするときなど…
しかし筆者が FastAPIに挑戦してみたところ、どの段階でも大きなストレスがないばかりか、非常に親切で感銘を受けた要素さえある
この記事を通して、筆者なりの解釈で FastAPIのいいところや実装のポイントなどをまとめてみる
対象読者
- Pythonでの開発経験がある人
- Webアプリケーションまたは REST APIの開発経験がある人
FastAPIとは?
FastAPIとは、Pythonベースの REST APIフレームワークである
Viewを伴わない REST APIの実装に特化している
その特徴・利点は以下の通り
- シンプルな構成
FastAPIのアーキテクチャはかなりシンプルにまとめられており、理解しやすいものとなっている - 少ないコード量で直感的に実装できる
- 型ヒントによる安全なコード
2023年現在、Pythonのコーディングでは型ヒントを付与する潮流がある
特に型安全なコーディングが求められる Web開発とは相性がいい
そういった背景から Pydanticの利用がデフォルトとなっており、バリデーションがサポートされている - 高速に動作する
- 自動でドキュメンテーションができる
Swagger UI、Redocによる APIドキュメントを自動で生成してくれる - 公式ドキュメントが非常に親切
日本語版はこちら
充実度・読みやすさともにすばらしい 👍
翻訳されていないページもあるかもしれないが、英語の文章もかなり読みやすい部類だと感じる
ドキュメントの質については意外と見落としがちだが、開発するうえでとても重要だと思う
後ほど取り上げるが、従来の APIフレームワークのいいとこ取りのようなものとなっている
構成要素
ここでは、FastAPIの構成要素について紹介していく
FastAPIの機能群は以下のようなものと解釈している
⚠️ ここで、この図についていくつか注意点がある
- 正確さよりも構成のわかりやすさを重視した図である
たとえば、FastAPIの中の “リクエスト・レスポンス処理” や “バリデーション” などは明確に分割できるものではないかもしれない
しかし、機能としては別物と捉えたほうがわかりやすいため、あえて分けて図示した - ORMは FastAPIにデフォルトで含まれているものではない
含まれていないにもかかわらず、なぜ図の中に ORMを入れたのかというと、2つの理由がある- ORMを用いた DBの操作は 2023年時点で一般的だから
ORM使わない派もいるということなので、あくまで 1つの意見として… - “バリデーション” と “DB” が直接やりとりをしているように表現すると少し違和感が生じ得ると判断したため
- ORMを用いた DBの操作は 2023年時点で一般的だから
上記を踏まえておくと、以下の解説をより適切に理解できることと思う
Uvicorn
HTTPリクエスト・レスポンスは ASGIサーバである Uvicornが扱う
ASGIとは (Asynchronous Server Gateway Interface) の略である
元々、Pythonにおいて Webサーバと Webアプリケーションがやりとりをするためのインタフェースとして WSGI (Web Server Gateway Interface) というものが定められていた
このインタフェースがあることで、サーバとアプリケーションのフレームワークが交換可能な部品になるという効果がある
WSGIに準拠さえしていれば別のツールに置き換えることができるため、用途に応じて最適化しやすくなるというわけだ
そんな WSGIを非同期通信に対応させたものとして ASGIが生まれた
そして、その ASGIに対応した Webサーバとして Uvicornが開発された
FastAPIでは Webサーバとして Uvicornを使うことが推奨されているもよう (参考)
Starlette
FastAPIの内部にて、ASGIサーバとやりとりするための実装には Starletteが用いられている
Starletteは ASGIに対応したアプリケーションを開発するためのフレームワーク・ツールキットである
以下のような特徴がある
- 高速に動作する
- 直感的に実装できる
非同期処理には asyncioの記法が使用できる
そういった点からもデファクトスタンダードに寄せる努力が見える - WebSocketに対応している
- GraphQLに対応している
- Cookieに対応している
高速に動作しつつ機能も十分で、目立った弱点がないため採用されているのだろう
Pydantic
扱うデータ (スキーマ) の定義とバリデーションには Pydanticが用いられている
また、ドキュメントの自動生成にも役立っている
以下のような特徴がある
- 直感的に実装できる
Pythonの型ヒントの記法でスキーマを定義することができる
独自の記法を覚える必要がなく、誰にとっても読みやすいコードとなる ☀️ - 高速に動作する
ORM
ORMとは Object Relational Mappingの略である
これを使うことで、DBの CRUD処理をオブジェクト指向のパラダイムと同じように扱うことができる
FastAPIの本質からは少し離れてしまうため、ここでは深く取り上げないこととする
ドキュメント生成
FastAPIは、スキーマに基づいて OpenAPI準拠のドキュメントを自動生成する
このドキュメントを参照するには /docs
または /redoc
のパスにアクセスすればいい
それぞれ例を画像で示す
実装と動作の感覚を掴む
特徴や構成要素などをざっと取り上げたので、次は実際にどのように実装し動作するのかを見ていきたい
それぞれ要点となる箇所をかいつまんで解説する
インストール
インストールは pipで簡単にできる
ただし、Python3.7以上でなければならない
pip install fastapi
pip install "uvicorn[standard]"
パスオペレーションの定義
まずは初歩的な例として、 /hello
に GETリクエストを送ったとき “hello” の文字列が返ってくるというパスオペレーションを作る
その場合、以下のようなコードで実装することができる
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
async def hello() -> str:
return "hello"
非常に直感的でわかりやすいことと思う
まず、 app = FastAPI()
で FastAPIのインスタンスを生成する
それさえできればもう Routeを実装できる
@app.get("/hello")
のようにデコレータで HTTPメソッドとパスを宣言し、好きな名前で関数を定義する
非同期な処理にしたい場合は async
を付ける
あとは好きな値を returnするだけだ
お試しの実装ができたので、次は動作させてみる
これを main.pyというファイルで保存したとする
実際に動作させるには、同じディレクトリで
uvicorn main:app --reload
のコマンドを実行すればいい
すると、デフォルトならそのホストの 8000番のポートで動作しているはず--reload
を付けておくと、変更を検知して自動でリロードしてくれる
これで以下のように動作するはずだ
$ curl "http://localhost:8000/hello"
"hello"
パラメータを受け取る
/hello
はただ “hello” という文字列を返すだけの初歩的な処理だった
次はクエリパラメータを受け取る処理を実装してみる
@app.get("/param")
def return_param(name: str) -> dict:
return {"name": name}
上記の通り、関数の引数を記述するとクエリを取得できるようになる
$ curl "http://localhost:8000/param?name=john"
{"name": "john"}
クエリを必須にしたくなければ name: str = None
のようにデフォルト値を渡しておくといい
なお、パスオペレーションの定義では def return_param(name: str) -> dict:
のように戻り値の型を宣言している
この宣言によって、FastAPIが自動で生成するドキュメントに戻り値の型を反映させることができる
試しに http://localhost:8000/docs にアクセスすると、自動生成されたドキュメントが表示される
いい感じ 👍
モデルの CRUD処理を実装する
次はモデルの CRUD処理を考える
itemというモデルを作り、 GET /items
や GET /items/1
などを実装する例を示す
スキーマの定義
まずは itemのスキーマを定義する
スキーマは pydanticの BaseModelを継承させたクラスとして記述する
from pydantic import BaseModel
class Item(BaseModel):
id: int
name: str
⚠️ なお、この記事では、ORMを用いたモデル本体の定義は省略して解説する
この先の解説においては、スキーマからインスタンスを生成することで、擬似的に CRUD処理を実装する
以降、スキーマのインスタンス化は ORMの処理と置き換えて読むといいかもしれない
モデルの GET処理を書いてみる
GET /items
を追記する例を示す
今回はスキーマからインスタンスを生成するため、常に固定の値を返す
from item_schema import Item
@app.get("/items")
def read_items() -> list[Item]:
items = [
Item(id=1, name="cheese"),
Item(id=2, name="milk"),
]
return items
先ほど定義した Itemのインスタンスを生成し、listにして返す処理となっている
実際には items = ...
が ORMの処理になるところだ
動作確認をすると、以下のような結果となるはず
$ curl "http://localhost:8000/items"
[{"id":1,"name":"cheese"},{"id":2,"name":"milk"}]
続いて、idを指定した GET /items/1
のような処理を追記する
@app.get("/items/{item_id}")
def read_item(item_id: int) -> Item:
item = Item(id=item_id, name="cheese")
return item
これで以下のように動作する
$ curl "http://localhost:8000/items/3"
{"id":3,"name":"cheese"}
POST, DELETEを実装する
他の HTTPメソッドも同じ使用感で実装できる
@app.post("/items")
def create_item(item: Item) -> Item:
new_item = Item(id=item.id, name=item.name)
return new_item
@app.delete("/items/{item_id}")
def delete_item(item_id: int) -> int:
# no operation
return item_id
順に動作確認の結果を記載する
$ curl -X POST "http://localhost:8000/items" -H "Content-Type: application/json" -d '{"id": 8, "name": "egg"}'
{"id":8,"name":"egg"}
$ curl -X DELETE "http://localhost:8000/items/6"
6
そして、これらはしっかりとドキュメントにも反映されている
何度見ても気持ちがいい 👍
思想と歴史的経緯
まとめに入る前に、この FastAPIの生まれた経緯や思想などについても触れておこうと思う
公式ドキュメントに 代替ツールから受けたインスピレーションと比較という素晴らしいページがあるため、これを要約して伝えることにする
まず、このページから察するに、FastAPIの開発者である tiangolo氏は REST API用のフレームワークをとにかく探し回って比較していたようだ
その経緯をまとめてくれているおかげで、FastAPIがなぜ生まれたのか、なぜこれほど高い完成度に仕上がっているのかが理解しやすい
FastAPIの主要な思想
- 自動でドキュメンテーションをする
これは Django REST Frameworkから着想を得たらしい - データのバリデーションをする
これは marshmallowから着想を得たもよう - フレームワークとして小さくシンプルに保つ
FastAPIは REST APIの構築に特化したシンプルなフレームワークとなっている
特定の目的にフォーカスした小さなツールにすることで、部品として他のものと組み合わせやすくなる
同時に高いパフォーマンス・保守性も実現しやすい
これは Flaskから学んだとのこと - デファクトスタンダードに近い書式にする
FastAPIでは、Python標準の型ヒントによってスキーマの定義とバリデーションができる
標準に近い書式を採用することで、読みやすい・移植しやすいといった多くの利点がある
FastAPIの生まれた経緯
先の思想はつまり、開発者である tiangolo氏が求めたことでもある
それらをうまく満たすものがなかったため FastAPIが生まれたわけだ
では、順に経緯を追っていこう
まず、機能として自動ドキュメンテーション・バリデーションを満たすものとしては、marshmallow・apispecといったものがあったとのこと
しかし、この時点では型ヒントが登場する前であったり、使い勝手の悪い点があったりしたらしい
次に、求めるものに近いフレームワークもあったが、それぞれ微妙にマッチしなかったようだ
- molten
自動ドキュメンテーションもバリデーションもできるが、独自色が強く可搬性が低い - hug
初めて型ヒントによるスキーマを導入したことが革新的だったが、OpenAPIといった標準とは違う - API Star
型ヒントからバリデーションもスキーマ生成もできる
しかも OpenAPIに準拠している
かなり理想に近く期待もしていたが、プロジェクトの方向性が変わってしまった
このように、どうしても要件を満たすフレームワークが見つからず、FastAPIが開発されるに至った
まとめてみると、やはり FastAPIが生態系の隙間を埋めるように誕生したことがわかる
元のページに「インスピレーションを受けた」という記述がたくさんあるように、上記のような既存ツールがあってこそ洗練されたアーキテクチャになったのだろう
まとめ
最後に、FastAPIの特徴・利点をおさらいする
- シンプルな構成
- 少ないコード量で直感的に実装できる
- 型ヒントによる安全なコード
- 高速に動作する
- 自動でドキュメンテーションができる
- 公式ドキュメントが非常に親切
ここまで、FastAPIの概要や使い方、思想などを紹介してきた
2023年時点で、Pythonで REST APIを構築するなら FastAPIは最良の選択肢なのではないかと感じる
特に、公式ドキュメントの質の高さには感動したといってもいい
初めてコントリビュータになりたいと思ったので、翻訳あたりからやってみようかなと思ったり… 🤔