FastAPIの開発体験がとてもよかったので紹介する

読み終えるまでの目安: 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” が直接やりとりをしているように表現すると少し違和感が生じ得ると判断したため

上記を踏まえておくと、以下の解説をより適切に理解できることと思う

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 /itemsGET /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の主要な思想

  1. 自動でドキュメンテーションをする
    これは Django REST Frameworkから着想を得たらしい
  2. データのバリデーションをする
    これは marshmallowから着想を得たもよう
  3. フレームワークとして小さくシンプルに保つ
    FastAPIは REST APIの構築に特化したシンプルなフレームワークとなっている
    特定の目的にフォーカスした小さなツールにすることで、部品として他のものと組み合わせやすくなる
    同時に高いパフォーマンス・保守性も実現しやすい
    これは Flaskから学んだとのこと
  4. デファクトスタンダードに近い書式にする
    FastAPIでは、Python標準の型ヒントによってスキーマの定義とバリデーションができる
    標準に近い書式を採用することで、読みやすい・移植しやすいといった多くの利点がある

FastAPIの生まれた経緯

先の思想はつまり、開発者である tiangolo氏が求めたことでもある
それらをうまく満たすものがなかったため FastAPIが生まれたわけだ

では、順に経緯を追っていこう

まず、機能として自動ドキュメンテーション・バリデーションを満たすものとしては、marshmallow・apispecといったものがあったとのこと
しかし、この時点では型ヒントが登場する前であったり、使い勝手の悪い点があったりしたらしい

次に、求めるものに近いフレームワークもあったが、それぞれ微妙にマッチしなかったようだ

  • molten
    自動ドキュメンテーションもバリデーションもできるが、独自色が強く可搬性が低い
  • hug
    初めて型ヒントによるスキーマを導入したことが革新的だったが、OpenAPIといった標準とは違う
  • API Star
    型ヒントからバリデーションもスキーマ生成もできる
    しかも OpenAPIに準拠している
    かなり理想に近く期待もしていたが、プロジェクトの方向性が変わってしまった

このように、どうしても要件を満たすフレームワークが見つからず、FastAPIが開発されるに至った
まとめてみると、やはり FastAPIが生態系の隙間を埋めるように誕生したことがわかる
元のページに「インスピレーションを受けた」という記述がたくさんあるように、上記のような既存ツールがあってこそ洗練されたアーキテクチャになったのだろう

まとめ

最後に、FastAPIの特徴・利点をおさらいする

  • シンプルな構成
  • 少ないコード量で直感的に実装できる
  • 型ヒントによる安全なコード
  • 高速に動作する
  • 自動でドキュメンテーションができる
  • 公式ドキュメントが非常に親切

ここまで、FastAPIの概要や使い方、思想などを紹介してきた
2023年時点で、Pythonで REST APIを構築するなら FastAPIは最良の選択肢なのではないかと感じる

特に、公式ドキュメントの質の高さには感動したといってもいい
初めてコントリビュータになりたいと思ったので、翻訳あたりからやってみようかなと思ったり… 🤔

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください