FastAPI
FastAPIとは
- 以下公式より引用
FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降でAPI を構築するための
モダンで、高速(高パフォーマンス)な、Web フレームワークです。
FastAPIの特徴
- 直感的で分かりやすい書き方
- Flaskに似ている
- コードがシンプル
- 公式ドキュメントの情報が豊富
- 基礎を抑えれば、公式ドキュメントを参考にステップアップできる
- 日本語訳も豊富
- 自動ドキュメント生成機能がいい(docs/redoc)
- Swagger UIとの連携
- バリデーション機能の実装が簡単
- 入力データが規定や仕様に合っているか確認が可能
- ハイパフォーマンスかつ他のフレームワークで標準掲載されていない機能を兼ね備えている
- タイプヒントを用いたバリデーション
- 非同期処理
- オリジン間リソース共有(CORS)
- GraphQL
- WebSocket
Install
公式にインストール方法の記載があるので則る
https://fastapi.tiangolo.com/ja/#_3
📁 ~/work ❯ pip install fastapi
📁 ~/work ❯ pip install uvicorn
📁 ~/work ❯ pip list | grep -ie package -e fastapi -e uvicorn
Package Version
fastapi 0.116.1
uvicorn 0.35.0
function
- テスト用に以下のmain.pyを作成する
from fastapi import FastAPI
app = FastAPI()
@app.get("/") # appから始まる処理をデコレーターという下の部分も含む
async def index(): # async関数は非同期処理を行うためのもの
return {"message": "Hello, World!"} # レスポンスとしてJSON形式でメッセージを返す
- サーバを起動する
- Uvicornを利用
- main:app main.pyの中のappを起動するという意味
- --reload コードを変える度に自動的にリロードするという意味
📁 ~/work/fastapi ❯ uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['/Users/anzai/work/fastapi']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [3107] using StatReload
INFO: Started server process [3138]
INFO: Waiting for application startup.
INFO: Application startup complete.
- 表示されたURLをブラウザで起動してmessageが表示されることを確認する
仕組みとしてはブラウザアクセス(GETメソッド)によって関数が起動して
returnで定義しているmessage一式がブラウザに表示される流れとなる
Swagger UI
SwaggerはRESTful APIを構築するためのオープンソースのフレームワークのこと
OpenAPI Initiative(OAI)が定義したフォーマット
Swagger UIはSwagger-Specを読み込んで、HTML形式でドキュメントを生成するためのツール
例えば先ほど起動したURLに/docsを付けてアクセスするとAPIのドキュメントが作成されているのが分かる
http://127.0.0.1:8000/docs
Swaggerで定義された内容を元にhtmlベースのドキュメントを生成してくれるツール
http://127.0.0.1:8000/redoc
パスパラメータ
パスパラメータはAPIのパスで指定するパラメータのこと
main.pyを以下に変更する
- 変更点 @app.get("/") → @app.get("/hello") に変更
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello") # /helloにアクセス
async def index():
return {"message": "Hello, World!"}
ちなみにmain.pyを変更したタイミングでuvicornは--reloadオプションを付与しているので
ログ上は以下の様に自動的にアプリが再起動される
WARNING: StatReload detected changes in 'main.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [3138]
INFO: Started server process [3428]
INFO: Waiting for application startup.
INFO: Application startup complete.
少し応用してみる
main.pyを以下に書き換えてみる
from fastapi import FastAPI
app = FastAPI()
@app.get("/countries/{country_name}")
async def country(country_name):
return {"country_name": country_name}
上記の場合パスパラメータから動的に変数を取り込んで、関数内の処理で利用することができる
結果としてアクセスするURLによってreturnを変えることができる
例)地域毎に処理を変えたい時、商品毎に処理を変えたい時
もう少し応用して引数の値を制限することもできる
以下の場合はcoutry_nameをstringで固定する
from fastapi import FastAPI
app = FastAPI()
@app.get("/countries/{country_name}")
async def country(country_name: str):
return {"country_name": country_name}
この状態で再度docsにアクセスすると
自動的にこのAPIに関するドキュメントが作成されていることが分かる
http://127.0.0.1:8000/docs

クエリパラメータ
URLの?以降に設定されるパラメータのこと
クエリーパラメータは複数設定することができる、その場合&で連結する
関数内でパスパラメータは無いが引数が設定されている場合、クエリパラメータになる
以下のmain.pyを作成する
from fastapi import FastAPI
app = FastAPI()
@app.get("/countries/")
async def country(country_name: str = "japan", country_no: int = 1):
return {
"country_name": country_name,
"country_no": country_no
}
URLにアクセスする
まずはクエリパラメータなしのURLにアクセスする
http://127.0.0.1:8000/countries/

この場合、関数でデフォルト値として設定している値がreturnとして表示されていることが分かる
逆にクエリパラメメータを明示的に指定してアクセスする
http://127.0.0.1:8000/countries/?country_name=china&country_no=2

クエリパラメータで指定した値がreturnしていることが分かる
パスパラメータ&クエリパラメータの混合
パスパラメータとクエリパラメータを混ぜることもできる
例えば以下のコードを記述してみる
from fastapi import FastAPI
app = FastAPI()
@app.get("/countries/{country_name}")
async def country(country_name: str = "japan", city_name: str = "tokyo"):
return {
"country_name": country_name,
"city_name": city_name
}
URLにアクセスするとパスパラメータjapanの場合、自動的にデフォルトのクエリパラメータが設定されることが分かる
http://127.0.0.1:8000/countries/japan

明示的に以下の様にcountry_name,city_nameを指定する
http://127.0.0.1:8000/countries/china?city_name=Beijing
パスパラメータとクエリパラメータで指定した値がreturnされる事が分かる

オプションパラメータ
main.pyを以下の様に変えてみる
- 先頭行にOptionalモジュールのimportを追記
- クエリパラメターの値country_name,country_noをOptionとして設定
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/countries/")
async def country(country_name: Optional[str] = None, country_no: Optional[int] = None):
return {
"country_name": country_name,
"country_no": country_no
}
クエリパラメータなしのURLにアクセスするとnullとなる
http://127.0.0.1:8000/countries/

リクエストボディ
クライアントからAPIへのリクエストを送る際のデータをリクエストボディという
そのリクエストに対してAPIがクライアントに返す内容をレスポンスボディという
以下のmain.pyを作成する
- BaseModelモジュールのimport
- Item classの定義(データの型を定義)
- postリクエストで取得した値を返す
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: int
tax: Optional[float] = None
app = FastAPI()
@app.post("/item/")
async def create_item(item: Item):
return item
この状態でdocsにアクセスすると、どの様な値をpostすれば良いのか確認できる
http://127.0.0.1:8000/docs

Try it outをするとリクエストとレスポンスも確認できる

例えば、POSTされたデータの中から特定の値を抜き出すこともできる
main.pyのreturnを以下に変えてみる
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: int
tax: Optional[float] = None
app = FastAPI()
@app.post("/item/")
async def create_item(item: Item):
return {"message": f"{item.name}は,税込価格{int(item.price*item.tax)}円です。"}
Try it outで試すと、特定の値を戻り値で抽出できていることが分かる

PythonでAPIを叩いてみる
APIを叩くためのモジュールを作成していく
call_api.py
- requests,jsonのモジュールをimport
- post先のurl,bodyを設定し、urlにjson整形したbodyをpost
- 返り値をjsonでprint
import requests
import json
def main():
url = "http://127.0.0.1:8000/item/" # API endpoint
body = {
"name": "spinach",
"description": "green vegetables",
"price": 400,
"tax": 1.1
}
res = requests.post(url, json.dumps(body))
print(res.json())
if __name__ == "__main__":
main()
実行結果
📁 ~/work/fastapi ❯ python call_api.py
{'message': 'spinachは,税込価格440円です。'}
リクエストボディを入れ子にする
main.py
- List moduleも追加
- classでデータ構造を複数定義
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
class ShopInfo(BaseModel):
name: str
location: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: int
tax: Optional[float] = None
class Data(BaseModel):
shop_info: Optional[ShopInfo] = None
items: List[Item]
app = FastAPI()
@app.post("/")
async def index(data: Data):
return {"data": data}
try it outしてみる
POSTしたデータ
{
"shop_info": {
"name": "test market",
"location": "tokyo"
},
"items": [
{
"name": "apple",
"description": "fruit",
"price": 50,
"tax": 1.1
},
{
"name": "carrot",
"description": "vegetable",
"price": 20,
"tax": 1.1
}
]
}
ポイントはデータ構造を定義していれば入れ子でも対応できる
バリデーション
FastAPIにおけるバリデーションとはデータの検証やチェックのこと
main.pyに以下の変更を加える
- Fieldモジュールをインポート
- nameの最小文字数、最大文字数を設定
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel, Field
class ShopInfo(BaseModel):
name: str
location: str
class Item(BaseModel):
name: str = Field(min_length=4, max_length=12) # 最小文字数4, 最大文字数12
description: Optional[str] = None
price: int
tax: Optional[float] = None
class Data(BaseModel):
shop_info: Optional[ShopInfo] = None
items: List[Item]
app = FastAPI()
@app.post("/")
async def index(data: Data):
return {"data": data}
docs上でもitemのnameに制限が加わったことを確認できる

では実際にテストとして最小文字数を満たさない文字数でテストしてみる

少なくとも4文字にする様に422errorが出ていることが確認できる
では条件を満たす4文字でリクエストを送ってみる

今度はステータスコードが200で問題ないことが確認できる
以上






