FastAPI は、Python 3.6以降を使用したモダンなアプリケーションプログラミングインターフェースの構築に便利な、高速かつ軽量のウェブフレームワークです。今回の記事では、そんなFastAPI を使ったアプリ構築の基本を説明し、これが2021年ベストオープンソースフレームワークの1つとしてノミネートされた理由の一端をご紹介します。

FastAPIアプリを開発する準備ができたら、サーバー選びが必要になります。Kinstaのアプリケーションホスティングデータベースホスティングサービスは、Pythonに強いPaaS(Platform as a Service)です。是非ともあわせてご確認ください。

まずは基本から学びましょう。

FastAPIの強み

はじめに、FastAPIフレームワークが各種開発プロジェクトにもたらす利点をご紹介します。

  • スピード:FastAPIは、その名の通り、非常に高速なフレームワークです。その速さは、一般にAPI構築の中でも最速の選択肢と称されるGoNode.jsに匹敵します。
  • 学習とプログラミングが容易:FastAPIでは、APIを世に送り出すために必要なほぼすべての要素が網羅されています。いちいちゼロからすべてを記述する必要はありません。わずか数行のコードで、デプロイ可能なRESTful APIを用意することができます。
  • 包括的なドキュメント:FastAPIはOpenAPIというドキュメントの仕様を採用しており、ドキュメントを動的に生成することができます。このドキュメントには、FastAPIのエンドポイント、応答、パラメータ、リターンコードといった詳しい情報が記載されます。
  • バグの少ないAPI:FastAPIはカスタムデータ検証をサポート。バグの少ないAPIを構築することができます。FastAPIの採用により人為的なバグが40%減少した例も見られるほどです。
  • 型ヒント:Python 3.5で導入された型ヒントにより、変数のtypeが宣言可能になりました。変数の型が宣言されていると、IDEにおけるサポート面での相性が高まり、より正確にエラーを予測することができます。

FastAPI利用の下準備

今回の記事の説明に沿ってFastAPIを利用するには、少しだけ準備をする必要があります。

Visual Studio Codeなどのプログラミング向けテキストエディタ/IDEをご用意ください。他には、Sublime TextEspressoなどもおすすめです。

Pythonアプリケーションとそのインスタンスの実行場所として仮想環境を構築するのが一般的です。仮想環境では、複数のパッケージセットや構成を同時に実行しながら、互換性のないパッケージのバージョンによる干渉を回避することができます。

仮想環境を作成するには、ターミナルを開いて次のコマンドを実行します。

$ python3 -m venv env

また、仮想環境を有効にする必要があります。これを行うコマンドは、使用しているオペレーティングシステムやシェルにより異なります。各環境でのコマンドは以下の通りです。

# UnixまたはMacOS(bashシェルスクリプト): 
/path/to/venv/bin/activate

# UnixまたはMacOS(cshスクリプト):
/path/to/venv/bin/activate.csh

# UnixまたはMacOS(fishシェル):
/path/to/venv/bin/activate.fish

# Windows(コマンドプロンプト):
pathtovenvScriptsactivate.bat

# Windows(PowerShell):
pathtovenvScriptsActivate.ps1

Python対応IDEの中には、仮想環境有効化の設定ができるものもあります。

続いては、FastAPIをインストールします。

$ pip3 install fastapi

FastAPIはAPI構築のフレームワークですが、APIをテストするためにはローカルウェブサーバーが必要になります。開発に便利な選択肢として、Uvicornをおすすめします。Uvicornをインストールするには、次のコマンドを実行します。

$ pip3 install "uvicorn[standard]"

インストールに成功したら、プロジェクトの作業ディレクトリにmain.pyという名前のファイルを作成します。このファイルがアプリケーションのエントリポイントになります。

IDE内の基本的なFastAPIプロジェクトの様子
IDE内の基本的なFastAPIプロジェクトの様子

FastAPIの簡単な例

エンドポイントの例をセットアップし、FastAPIをテストしてみましょう。main.pyファイルに以下のコードを貼り付けて、ファイルを保存します。

# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
 return {"greeting":"Hello world"}

上記コードにより、基本的なFastAPIエンドポイントが作成できます。説明は以下の通りです。

  • from fastapi import FastAPI:APIの機能がFastAPIというPythonクラスで提供される
  • app = FastAPI():FastAPIインスタンスを作成
  • @app.get("/"):Pythonのデコレータであり、FastAPIに対して、その下の関数がリクエスト処理を担当することを指定
  • @app.get("/"):ルートを指定するデコレータ(サイトのルートでGETメソッドを作成し、ラップされた関数により結果が返される)
  • このほかにも、@app.post()@app.put()@app.delete()@app.options()@app.head()@app.patch()@app.trace()のような操作を行うことが可能

ファイルのあるディレクトリで、ターミナルのコマンドを実行しAPIサーバーを起動します。

$ uvicorn main:app --reload

このコマンドにあるmainは、モジュールの名前です。appオブジェクトはアプリケーションのインスタンスであり、ASGIサーバーにインポートされます。--reloadは、何か変更を加えたときに自動での再読み込みをサーバーに指示するものです。

ここまでで、ターミナルには以下が表示されるはずです。

 $ uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['D:\WEB DEV\Eunit\Tests\fast-api']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [26888] using WatchFiles
INFO: Started server process [14956]
INFO: Waiting for application startup.
INFO: Application startup complete.

続いてブラウザで、http://localhost:8000に移動して、APIが動作していることを確認してください。JSONオブジェクトとして「“Hello”: “World”」と表示されます。この段階ですでに、FastAPIを使ったAPIの作成がいかに簡単かをご理解いただけたはずです。上記コードの6行目にあるように、ルートを定義し、Pythonの辞書を返すという操作を行うだけで完了しています。

FastAPIでHello Worldを表示してみる
FastAPIでHello Worldを表示してみる

型ヒントを使う

Pythonでのコード記述の経験があれば、変数にintstrfloatboolのような基本的なデータ型を付けることには慣れているはずです。しかし、Pythonバージョン3.9からは、高度なデータ構造が導入されており、dictionariestupleslists などのデータ構造での作業が可能になっています。FastAPIの型ヒントを使用すると、pydanticモデルを使用してデータのスキーマを構成し、データ検証の恩恵を受けることができます。

以下の例で、シンプルな食事の価格計算機calculate_meal_feeを使って、Pythonでの型ヒントの使い方をご紹介します。

def calculate_meal_fee(beef_price: int, meal_price: int) -> int:
 total_price: int = beef_price + meal_price
 return total_price
print("Calculated meal fee", calculate_meal_fee(75, 19))

 

ちなみに、型ヒントによってコードの挙動そのものは変更されません。

FastAPIのインタラクティブAPIドキュメント

FastAPIでは、Swaggerを使用した自動での対話型APIドキュメント生成が可能です。http://localhost:8000/docsに移動すると、すべてのエンドポイント、メソッド、スキーマを確認できます。

Swaggerを使ったFastAPIのドキュメント
Swaggerを使ったFastAPIのドキュメント

このブラウザベースのAPIドキュメントはFastAPIで自動生成されるので、これの用意として特別な操作をする必要はありません。

ブラウザベースのAPIドキュメントには、Redocという選択肢もあります。これを利用するには、http://localhost:8000/redocに移動してください。すると、エンドポイント、メソッド、およびそれぞれの応答が一覧で表示されます。

Redocを使ったFastAPIのドキュメント
Redocを使ったFastAPIのドキュメント

FastAPIでのルートの設定

@appデコレータでは、ルートのメソッド@app.get@app.postのように指定できます。また、GETPOSTPUTDELETEや、あまり一般的ではない選択肢であるHEADPATCHTRACEもサポートしています。

FastAPIでアプリを構築する

続いては、FastAPIを使用してCRUDアプリケーションを構築する方法をご紹介します。このアプリケーションでは以下のことができます。

  • ユーザーを作成する
  • ユーザーのデータベースレコードを読み込む
  • 既存のユーザーの情報を変更する
  • 既存のユーザーを削除する

CRUDの各操作を実行するために、APIエンドポイントを公開するメソッドを作成します。その結果として、ユーザーのリストを保存できるインメモリデータベースが出来上がります。

CRUDの例(データベーステーブルの構造)
CRUDの例(データベーステーブルの構造)

Pythonの型注釈を使ってデータの検証や設定管理を行うために、pydanticライブラリを使用します。この例では、データの形状を属性を持つクラスとして宣言します。

インメモリデータベースを使用しましょう。これにより、FastAPIを使いながらAPIを素早く構築することができます。とは言え、実際の開発環境では、PostgreSQLMySQLSQLite、あるいはOracleなど、好みのデータベースを利用していただいてかまいません。

アプリの構築

まず、ユーザーモデルを作成します。ユーザーモデルには、次のような属性があります。

  • id:UUID(汎用一意識別子)
  • first_name:ユーザーの名前
  • last_name:ユーザーの苗字
  • gender:ユーザーの性別
  • rolesadminuserという役割がこれに含まれる

まず、作業ディレクトリにmodels.pyという名前のファイルを作成し、次のコードをmodels.pyに貼り付けてモデルを作成します。


# models.py
from typing import List, Optional
from uuid import UUID, uuid4
from pydantic import BaseModel
from enum import Enum
from pydantic import BaseModel
class Gender(str, Enum):
 male = "male"
 female = "female"
class Role(str, Enum):
 admin = "admin"
 user = "user"
class User(BaseModel):
 id: Optional[UUID] = uuid4()
 first_name: str
 last_name: str
 gender: Gender
 roles: List[Role]

上のコードを簡単に説明すると以下の通りです。

  • UserクラスがBaseModelを継承し、これがpydanticからインポートされる
  • 上述のように、ユーザーの属性を定義

次のステップは、データベースの作成です。main.pyファイルの中身を以下のコードに置き換えてください。


# main.py
from typing import List
from uuid import uuid4
from fastapi import FastAPI
from models import Gender, Role, User
app = FastAPI()
db: List[User] = [
 User(
 id=uuid4(),
 first_name="John",
 last_name="Doe",
 gender=Gender.male,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="Jane",
 last_name="Doe",
 gender=Gender.female,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="James",
 last_name="Gabriel",
 gender=Gender.male,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="Eunit",
 last_name="Eunit",
 gender=Gender.male,
 roles=[Role.admin, Role.user],
 ),
]

main.pyで以下の操作を行っています。

  • dbListの型で初期化し、Userのモデルを渡す
  • それぞれfirst_namelast_namegenderrolesのような必要とされる属性を持つ4人のユーザーを格納するインメモリデータベースを作成(ユーザーEunitにはadminuserの役割が割り当てられ、他の3人のユーザーにはuserのみ割り当てられる)

データベースレコードの読み出し

ここまでで、インメモリデータベースのセットアップとユーザーの登録が完了しました。次のステップで、すべてのユーザーの一覧を返すエンドポイントをセットアップします。FastAPIの腕の見せどころです。

main.pyファイルで、Hello Worldエンドポイントのすぐ下に次のコードを貼り付けます。


# main.py
 @app.get("/api/v1/users")
 async def get_users():
 return db

このコードによりエンドポイント/api/v1/usersが定義され、また、データベースdbのすべての内容を返す非同期関数get_usersが作成されます。

ファイルを保存すると、ユーザーエンドポイントのテストが可能です。ターミナルで次のコマンドを実行し、APIサーバーを起動してみましょう。

$ uvicorn main:app --reload

ブラウザで、http://localhost:8000/api/v1/usersにアクセスします。以下のように、すべてのユーザーの一覧が表示されるはずです。

FastAPIのデータベース読み込みリクエストでユーザーデータを取得
FastAPIのデータベース読み込みリクエストでユーザーデータを取得

この段階で、main.pyファイルは以下のようになります。


# main.py
from typing import List
from uuid import uuid4
from fastapi import FastAPI
from models import Gender, Role, User
app = FastAPI()
db: List[User] = [
 User(
 id=uuid4(),
 first_name="John",
 last_name="Doe",
 gender=Gender.male,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="Jane",
 last_name="Doe",
 gender=Gender.female,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="James",
 last_name="Gabriel",
 gender=Gender.male,
 roles=[Role.user],
 ),
 User(
 id=uuid4(),
 first_name="Eunit",
 last_name="Eunit",
 gender=Gender.male,
 roles=[Role.admin, Role.user],
 ),
]
@app.get("/")
async def root():
 return {"Hello": "World",}
@app.get("/api/v1/users")
async def get_users():
 return db

データベースレコードの作成

次のステップは、データベースでの新規ユーザー作成のためのエンドポイント作成です。次のコードをmain.pyファイルに貼り付けてください。


# main.py
@app.post("/api/v1/users")
async def create_user(user: User):
 db.append(user)
 return {"id": user.id}

このコードでは、新規ユーザー登録用のエンドポイントを定義し、@app.postデコレータを使用しPOSTメソッドを作成しています。

また、Userモデルのuserを受け取る関数create_userを作成し、新規作成したuserをデータベースdbに追加しています。最後に、このエンドポイントからは、作成したユーザーのidのJSONオブジェクトが返されます。

上述のように、エンドポイントをテストするには、FastAPIの自動APIドキュメントを使用します。これは、ウェブブラウザを使用しPOSTを実行できないためです。http://localhost:8000/docsに移動することで、Swaggerを利用したドキュメントを確認できます。

FastAPIのPOSTリクエストのパラメータを確認する
FastAPIのPOSTリクエストのパラメータを確認する

データベースレコードの削除

今回の例では、CRUDアプリケーションの構築を行っているため、アプリケーションには指定したリソースを削除する機能が必要です。それでは、ユーザー削除のエンドポイントを作成しましょう。

以下のコードをmain.pyファイルに貼り付けます。


# main.py
from uuid import UUID
from fastapi HTTPException
@app.delete("/api/v1/users/{id}")
async def delete_user(id: UUID):
for user in db:
 if user.id == id:
 db.remove(user)
 return
raise HTTPException(
 status_code=404, detail=f"Delete user failed, id {id} not found."
 )

このコードで行っていることは以下の通りです。

  • @app.delete("/api/v1/users/{id}")@app.delete()デコレータを使用してdeleteエンドポイントを作成(パスは/api/v1/users/{id}のままですが、その後、ユーザーのidに対応するパス変数であるidを取得)
  • async def delete_user(id: UUID):URLからidを取得するdelete_user関数を作成
  • for user in db::データベース内のユーザーをループし、渡されたidがデータベース内のユーザーと一致するかどうかをチェック
  • db.remove(user)idがユーザーと一致した場合、そのユーザーを削除する(そうでない場合、ステータスコード404のHTTPExceptionが発生)
FastAPIのDELETEリクエストのパラメータを確認する
FastAPIのDELETEリクエストのパラメータを確認する

データベースレコードの更新

続いては、ユーザーの情報を更新するエンドポイントを作成します。更新対象のパラメータはfirst_namelast_namerolesです。

models.pyファイルで、次のコードをUserモデルの下に、つまりUser(BaseModel):クラスの後に貼り付けます。

 # models.py
 class UpdateUser(BaseModel):
 first_name: Optional[str]
 last_name: Optional[str]
 roles: Optional[List[Role]]

このコードで、クラスUpdateUserBaseModelを継承しています。そして、first_namelast_namerolesのような編集対象となるユーザーパラメータをオプションに設定します。

次に、特定のユーザー情報の更新に使用するエンドポイントを作成します。main.pyファイル内、@app.deleteデコレータの後に、次のコードを貼り付けます。

# main.py
@app.put("/api/v1/users/{id}")
async def update_user(user_update: UpdateUser, id: UUID):
 for user in db:
 if user.id == id:
 if user_update.first_name is not None:
 user.first_name = user_update.first_name
 if user_update.last_name is not None:
 user.last_name = user_update.last_name
 if user_update.roles is not None:
 user.roles = user_update.roles
 return user.id
 raise HTTPException(status_code=404, detail=f"{id}に該当するユーザーが見つかりませんでした")

上のコードでは、以下のことを行っています。

  • 情報編集のエンドポイントである@app.put("/api/v1/users/{id}")を作成(ユーザーのIDに対応する変数パラメータidあり)
  • update_userというメソッドを作成(UpdateUserクラスとidを取得)
  • forループを使用して、渡されたidに紐付けられたユーザーがデータベースに存在するかどうかをチェック
  • ユーザーのパラメータのいずれかがis not None(nullでない) かどうかをチェック(first_namelast_namerolesなど、いずれかのパラメータがnullでない場合そのパラメータが変更される)
  • 操作に成功すると、ユーザーIDが返される
  • ユーザーが見つからなかった場合、ステータスコード404(そしてメッセージ{id}に該当するユーザーが見つかりませんでした)のHTTPExceptionが表示される

このエンドポイントのテストの際には、Uvicornサーバーが稼働していることを確認しましょう。起動していない場合には、次のコマンドを入力してください。

uvicorn main:app --reload

テスト結果のスクリーンショットが以下の通りです。

FastAPIのUPDATEリクエストのパラメータを確認する
FastAPIのUPDATEリクエストのパラメータを確認する

まとめ

今回の記事では、PythonのFastAPIフレームワークの基礎、そして、FastAPI を使用したアプリケーションの素早い立ち上げの例をご紹介しました。具体的には、フレームワークを使用した、データベースレコードの作成、読み出し、更新、削除といったCRUD APIエンドポイントの構築を扱いました。

ウェブアプリ開発を次のレベルに引き上げるには、アプリケーションホスティングとデータベースホスティングをはじめとする、Kinstaのプラットフォームをご活用ください。FastAPIと同様に、シンプルかつ高性能です。

Emmanuel Uchenna

長年活動を続ける経験豊富かつ熱心なソフトウェア開発者、テクニカルライター。フルスタックウェブ開発を専門とする。ReactJS、JavaScript、VueJS、NodeJS、そしてGit、GitHub、TDDのような業界標準技術に精通。オンラインプレゼンスを高めたい個人や企業、ブランド向けに、レスポンシブでアクセシビリティ優れた魅力的なウェブサイトの構築を支援。複数のウェブサイトそして自身のプロジェクトで、テクニカルライターとしても活動。