Flaskユーザー向けFastAPIの使い方まとめ
はじめに
本記事は、「FastAPI for Flask Users」を翻訳したものになります。
この記事では、FlaskとFastAPIを比較しながらわかりやすく解説した記事なので、許可をとって翻訳することにしました。
以下、本文になります。
記事本文
Flaskは機械学習プロジェクトでのAPI開発の事実上の選択肢となっていますが、
FastAPIと呼ばれる新しいフレームワークがあり、コミュニティーから多くの支持を得ています。
最近、Flaskで作成された本番プロジェクトを移行することになり、FastAPIを試してみることにしました。
FastAPIはFlaskライクな文法で記述されているので、移し替えが非常に簡単で、わずか数時間で起動して実行できました。
自動データ検証、ドキュメント生成、およびpydanticスキーマやpythonタイピングなどのベストプラクティスの追加により、これは将来のプロジェクトにとって強力な選択肢となります。
本稿では、FlaskとFastAPIの両方でさまざまな一般的なユースケースの実装を対比して紹介します。
準備
バージョン情報:
本稿の執筆時点では、Flaskのバージョンは1.1.2で、FastAPIのバージョンは0.58.1です。
インストール
FlaskとFastAPIの両方がPyPI(通常のpipによるインストール)で利用できます。
condaを使用する場合、conda-forge
のチャンネルを利用する必要がありますが、FastAPIは、Flaskのデフォルトチャネルで利用することができます。
通常のインストール
Flask
$ pip install flask
FastAPI
$ pip install fastapi uvicorn
condaを使ったインストール
Flask
$ conda install flask
FastAPI
$ conda install fastapi uvicorn -c conda-forge
比較
Hello World
Flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
上記を、以下のコマンドを使用して実行できます。デフォルトではポート5000で実行できます。
$ python app.py
FastAPI
# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
FastAPIは、デフォルトポート8000で次のように実行できます。
$ python app.py
本番サーバ
Flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
本番サーバーの場合、Flaskでは一般にgunicorn
を利用します。
$ gunicorn app:app
次のコマンドを実行して、ホットリロードモードで起動することもできます。
$ gunicorn app:app --reload
FastAPI
# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
FastAPIは、本番対応サーバへのサービス提供を行うuvicorn
を使い、次のように実行します。
$ uvicorn app:app
次のコマンドを実行して、ホットリロードモードで起動することもできます。
$ uvicorn app:app --reload
HTTPメソッド
Flask
@app.route('/', methods=['POST'])
def example():
...
FastAPI
@app.post('/')
def example():
...
FastAPIでは、HTTPメソッドごとに個別のデコレータメソッドがあります。
@app.get('/')
@app.put('/')
@app.patch('/')
@app.delete('/')
URL変数
URLからユーザーIDを取得します。 /users/1
その後、ユーザーIDをユーザーに返します。
Flask
@app.route('/users/<int:user_id>')
def get_user_details(user_id):
return {'user_id': user_id}
FastAPI
FastAPIでは、Pythonの型ヒント(Type Hints) を使用してすべてのデータ型を指定します。例えば、ここではuser_id
が整数であることを指定しています。 URLパスの変数もf-stringsと同様に指定できます。
@app.get('/users/{user_id}')
def get_user_details(user_id: int):
return {'user_id': user_id}
クエリストリング
ユーザーがURLにクエリストリング?q=abc
を指定したい場合は次のようにします。
Flask
# app.py
from flask import request
@app.route('/search')
def search():
query = request.args.get('q')
return {'query': query}
FastAPI
# app.py
@app.get('/search')
def search(q: str):
return {'query': q}
JSON POSTリクエスト
text
をkey、アッパーケースをvalue
とするJSONをPOSTリクエストで送信して、ローワーケースのvalueを取得する簡単な例を見てみましょう。
# 指定するリクエスト
{"text": "HELLO"}
# 期待するレスポンス
{"text": "hello"}
Flask
# app.py
from flask import request
@app.route('/lowercase', methods=['POST'])
def lower_case():
text = request.json.get('text')
return {'text': text.lower()}
FastAPI
Flaskから機能を複製する場合、FastAPIでは次のように実行できます。
# app.py
from typing import Dict
@app.post('/lowercase')
def lower_case(json_data: Dict):
text = json_data.get('text')
return {'text': text.lower()}
しかし、ここでFastAPIではpydanticスキーマーという新しい概念を導入して、リクエストされたJSONデータをマップするスキーマーを作成します。pydanticを使い上記の例をリファクタリングするとこのようになります。
# app.py
from pydantic import BaseModel
class Sentence(BaseModel):
text: str
@app.post('/lowercase')
def lower_case(sentence: Sentence):
return {'text': sentence.text.lower()}
このように、辞書を取得する代わりに、JSONデータはスキーマSentence
のオブジェクトに変換されます。
また、sentence.text
のようなデータ属性を使ってデータにアクセスすることができます。これにより、データ型の自動検証も可能になります。
ユーザーが文字列以外のデータを送ろうとすると、自動生成されたバリデーションエラーが発生します。
無効なリクエストの例
例では、stringではない値、nullを指定しました。
{"text": null}
自動応答
{
"detail": [
{
"loc": [
"body",
"text"
],
"msg": "none is not an allowed value",
"type": "type_error.none.not_allowed"
}
]
}
ファイルのアップロード
アップロードされたファイル名を返すAPIを作成しましょう。
ファイルのアップロード時に使用されるキーはfile
です。
Flask
Flaskを使用すると、リクエストオブジェクトを介してアップロードされたファイルにアクセスできます。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files.get('file')
return {'name': file.filename}
FastAPI
FastAPIは、関数にインポートしたFile型を引数に指定します。
# app.py
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
@app.post('/upload')
def upload_file(file: UploadFile = File(...)):
return {'name': file.filename}
フォームの送信
以下に示すように定義されたテキストフォームのフィールドにアクセスして値を返します。
<input name='city' type='text'>
Flask
Flaskでは、リクエストオブジェクトを介してフォームのフィールドにアクセスできます。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def echo():
city = request.form.get('city')
return {'city': city}
FastAPI
インポートしたForm型を引数に指定して、フォームフィールドのキーとデータ型を定義します。
# app.py
from fastapi import FastAPI, Form
app = FastAPI()
@app.post('/submit')
def echo(city: str = Form(...)):
return {'city': city}
以下に示すように、フォームフィールドをオプションにすることもできます。
# app.py
from typing import Optional
@app.post('/submit')
def echo(city: Optional[str] = Form(None)):
return {'city': city}
同様に、以下に示すように、フォームフィールドに初期値を設定できます。
# app.py
@app.post('/submit')
def echo(city: Optional[str] = Form('Paris')):
return {'city': city}
クッキー
name
リクエストから呼び出されたCookieにアクセスしたいと思います。
Flask
Flaskを使用すると、リクエストオブジェクトを介してCookieにアクセスできます。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/profile')
def profile():
name = request.cookies.get('name')
return {'name': name}
FastAPI
インポートしたCookie型を引数に指定して、Cookieのキーname
を定義します。
# app.py
from fastapi import FastAPI, Cookie
app = FastAPI()
@app.get('/profile')
def profile(name = Cookie(None)):
return {'name': name}
Modular Views
単一のapp.py
からのビューを別々のファイルに移動し、アプリケーションの機能を分解したいと思います。
肥大化したプロジェクトを整理することができるためFlaskではこの機能が推奨されています。
Flask
- app.py
- views
- user.py
Flaskでは、Blueprintと呼ばれる概念を使用してこれを管理します。
まず、クライアント側のBlueprintを次のように作成します。
# views/user.py
from flask import Blueprint
user_blueprint = Blueprint('user', __name__)
@user_blueprint.route('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
次に、このビューをメインapp.pyファイルに登録します。
# app.py
from flask import Flask
from views.user import user_blueprint
app = Flask(__name__)
app.register_blueprint(user_blueprint)
FastAPI
- app.py
- routers
- user.py
FastAPIでは、Blueprintに相当するものはルーターと呼ばれます。
例では、次のようにユーザールーターを作成します。
# routers/user.py
from fastapi import APIRouter
router = APIRouter()
@router.get('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
次に、このルーターをメインのアプリオブジェクトに次のように接続します。
# app.py
from fastapi import FastAPI
from routers import user
app = FastAPI()
app.include_router(user.router)
データの型検証
Flask
Flaskは、デフォルトで入力データ検証機能を提供していません。カスタム検証ロジックを作成するか、marshmalllowやpydantic などのライブラリを使用するのが一般的な方法です。
FastAPI
FastAPIは、pydanticをフレームワークにラップし、pydanticスキーマとPython型ヒントの組み合わせを使用するだけでデータ検証を可能にします。
# app.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post('/users')
def save_user(user: User):
return {'name': user.name,
'age': user.age}
このコードは自動検証を実行してname
が文字列でage
が整数であることを確認します。他のデータ型が送信されると、関連するメッセージとともに検証エラーが自動生成されます。
以下では、一般的なユースケースのpydanticスキーマの例を示します。
例1:キーと値のペア
{
"name": "Isaac",
"age": 60
}
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
例2:リスト
{
"series": ["GOT", "Dark", "Mr. Robot"]
}
from pydantic import BaseModel
from typing import List
class Metadata(BaseModel):
series: List[str]
例3:ネストされたオブジェクト
{
"users": [
{
"name": "xyz",
"age": 25
},
{
"name": "abc",
"age": 30
}
],
"group": "Group A"
}
from pydantic import BaseModel
from typing import List
class User(BaseModel):
name: str
age: int
class UserGroup(BaseModel):
users: List[User]
group: str
Pythonタイプのヒントについて詳しくは、こちらをご覧ください。
自動ドキュメント
Flask
Flaskには、ドキュメント生成用の組み込み機能はありません。そのギャップを埋めるためにflask-swagger やflask-restfulなどの拡張機能がありますが、ワークフローは比較的複雑です。
FastAPI
FastAPIでは、直感的でブラウザから操作可能なSwaggerドキュメントのエンドポイント/docs
とReDocドキュメントのエンドポイント/redoc
が用意されており、これらは自動的に生成されます。
例えば、以下のようなユーザーが検索したものを返す簡単なAPIがあるとします。
# app.py
from fastapi import FastAPI
app = FastAPI()
@app.get('/search')
def search(q: str):
return {'query': q}
Swaggerドキュメント
サーバーを起動してエンドポイントhttp://127.0.0.1:8000/docs
にアクセスすると、自動生成されたSwaggerドキュメントが表示されます。
ブラウザから直接APIを試すことができます。
ReDocドキュメント
同様に、サーバー起動後エンドポイントhttp://127.0.0.01:8000/redoc
にアクセスすると、自動生成されたリファレンスドキュメントが表示されます。
ここでは、パラメータ、リクエスト形式、レスポンス形式、ステータスコードに関する情報があります。
オリジン間リソース共有(CORS)
異なるオリジンからのアクセスを許可する方法を示します。
Flask
Flaskは、デフォルトでCORSのサポートを提供していません。以下に示すように、CORSを構成するには、flask-corsなどの拡張機能を使用する必要があります。
# app.py
from flask import Flask
from flask_cors import CORS
app_ = Flask(__name__)
CORS(app_)
FastAPI
FastAPIは、CORSに対応するための組み込みミドルウェアが提供されています。
以下に、任意のorigin
がAPIにアクセスすることを許可するCORSの例を示します。
# app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
*
を任意のドメインに置き換えることでアクセスを制限することができます。
おわりに
FastAPIは、ベストプラクティスが組み込まれた堅牢なAPIを構築するための、Flaskの優れた代替手段です。詳細については、ドキュメントを参照してください。
Discussion