Python

【Python】FastAPIを使ったWeb APIの開発

環境

・M2 Mac
・macOS Monterey
・shell:zsh

FastAPI

「FastAPI」は、Python 3.6以降でWeb APIを開発するための「Webフレームワーク」(Webアプリケーションを開発するために必要な機能をパッケージ化したもの。)の一つです。

FastAPIのインストール

FastAPI及び必要なライブラリをインストールします。

% pip3 install "fastapi[all]"

 

または、

# メインのライブラリをインストール。
% pip3 install fastapi

# コードを実行して起動するサーバーのインストール。
% pip3 install "uvicorn[standard]"

 

シンプルなAPIの作成

ローカルサーバー(ここでは自身のPC内に構築したサーバーをいいます。)でGETメソッドを実行するとJSON形式のデータが返される最もシンプルなAPIを作成します。

まず、「main.py」ファイルを作成し、以下のプログラムを入力します。コメントにて各プログラムの内容を説明していきます。

from fastapi import FastAPI  # FastAPIクラスをインポートします。

app = FastAPI()  # FastAPIクラスからインスタンスを生成します。

# @の行はデコレータです。
# インスタンス化したappに対し、HTTPメソッド(「オペレーション」ともいいます。)のGETでルート(/)にアクセスがあった場合、直下の関数の処理を行います。
@app.get("/")  # ()内を「パス」といいます。
async def root():  # asyncは非同期処理を可能とし、ある処理が実行中でも他の処理を実行することができます。
    return {"message": "Hello World"}

 

@の行は、パスに対するオペレーションが定義されたデコレータですので、「パスオペレーションデコレータ」といいます。また、デコレータ直下の関数を「パスオペレーション関数」といいます。

次に、uvicornを使用しローカルサーバーを起動します。

% uvicorn main:app --reload  # mainはファイル名、appはインスタンスを指します。


# 実行結果
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

 

「main.py」のappインスタンスを起動しました。「--reload」は開発用のオプションで、コード変更時にサーバーの再起動を自動的に行なってくれます。

実行結果の1行目にある、「http://127.0.0.1:8000」(このURLはルート「/」と同一です。つまり「http://127.0.0.1:8000/」と同じアドレスを意味します。)にWebブラウザでアクセス(GETメソッドを実行)すると、ブラウザにJSON形式のデータ「{"message": "Hello World"}」が表示されます。

仮に、パス部分を「"/home”」とした場合は、「http://127.0.0.1:8000/home」で 「{"message": "Hello World”}」が表示されます。

パスパラメータ

「パスパラメータ」を使用することで、パスの一部を変数として受け取り、パスオペレーション関数の引数として使用することができます。

先ほど、作成した「main.py」に以下のプログラムを追記します。

# パスのうち{}で囲われた部分を「パスパラメータ」といいます。パスの一部を変数として受け取ります。
# パスパラメータはパスオペレーション関数の引数として使用できます。
@app.get("/items/{item_id}")
async def read_item(item_id):
    return {"item_id": item_id}

 

ここで、「http://127.0.0.1:8000/items/1」にアクセスするとブラウザに「{"item_id": 1}」が表示されます。

なお、パスオペレーション関数の引数に「アノテーション」を設定することで、設定した型以外の型がパスで指定された場合、エラーが出力されます。通常の関数のアノテーションではエラーは出力されません。FastAPIが、データの「バリデーション(検証)」を行なっているのです。

@app.get("/items/{item_id}")
async def read_item(item_id: int):  # パスパラメータにint型以外が与えられた場合、エラーが表示されます。
    return {"item_id": item_id}

 

以下は、コンテナにアノテーションを施す方法です。

li: list[int] = [1, 2, 3]  # int型を要素とするリスト。

tu: tuple[str, int] = ('a', 1)  # str型とint型を要素とするタプル。

se: set[str] = {'a', 'b', 'c'}  # str型を要素とする集合。

di: dict[str, int] = {'a': 1, 'b': 2, 'c': 3}  # str型のキーとint型の値を持つ辞書。

 

クエリパラメータ

URL内で「?」以降に付与されるパラメータを「クエリパラメータ」といいます。なお、検索キーワードのことを「クエリ」ともいいます。

パスパラメータではない変数をパスオペレーション関数の引数に設定すると自動的にクエリパラメータとして認識されます。

fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]

@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
    return fake_items_db[skip : skip + limit]  # クエリパラメータを使ったスライスでfake_items_dbから要素を抽出します。

 

クエリパラメータは、URL内において「?クエリパラメータ1=クエリパラメータ1の値&クエリパラメータ2=クエリパラメータ2の値&...」として表記することができます。

また、上記プログラムのクエリパラメータはデフォルト値を持っているため、「http://127.0.0.1:8000/items/」は「http://127.0.0.1:8000/items/?skip=0&limit=10」へのアクセスと同等となります。

仮に、「http://127.0.0.1:8000/items/?skip=1&limit=1」にアクセスした場合、「fake_items_db[1: 2]」の値が返され、ブラウザには「[{"item_name": "Bar"}]」が表示されます。

オプションパラメータ

APIを叩く際に、必須ではないパラメータを「オプションパラメータ」といいます。

「Union型」を使いオプションパラメータを設定していきます。Union型は「typingモジュール」で定義された型で、複数のデータ型を宣言することができます。例えば、文字列型または整数型を宣言したい場合は「Union[str, int]」と表記します。

from typing import Union
from fastapi import FastAPI

@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

 

Union[str, None]によって、文字列型かNone型を宣言しています。None型は値「None」のみを持ち、Noneは値が存在しないことを表します。

オプショナル(必須ではない)なクエリパラメータであるqに値を指定しなかった場合、qはデフォルト値であるNoneを持ちます。

リクエストボディ

POSTメソッド(クライアントからAPIにデータを送信するメソッド。)を用いてリクエストを送る際、データ本体を「リクエストボディ」として送信します(逆にAPIは、クライアントにレスポンスを返す際、データ本体を「レスポンスボディ」として送信します。)。

POSTメソッドでは、基本的にJSON形式のリクエストボディを扱うことになります。

リクエストボディは、「pydanticモジュール」「BaseModelクラス」を継承したクラスで、その構造(モデル)を定義していきます。

from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel  # pydanticモジュールのBaseModelクラスをインポートします。

# BaseModelクラスを継承したクラスで、データモデルを定義します。
# データモデルの内容は変数とアノテーションで構成します。
class Item(BaseModel):
    name: str
    description: Union[str, None] = None  # 必須ではないパラメータです。
    price: float
    tax: Union[float, None] = None  # 必須ではないパラメータです。


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):  # 引数itemはItem型である必要があります。item.name等とすることで各パラメータにアクセスすることもできます。
    return item  # リクエストボディをレスポンスボディとして返します。

 

上記プログラムで作成したAPIを実際に叩いて見ます。以下のプログラムを別のターミナルで実行してください。

import requests

url = 'http://127.0.0.1:8000/items/'

body = {
    "name": "Foo",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}

response = requests.post(url, json=body)
print(response.json())


# 実行結果
{'name': 'Foo', 'description': 'An optional description', 'price': 45.2, 'tax': 3.5}