FastAPI とは?
FastAPIとはpython3.6以上を対象とした、APIを作成するためのモダンで処理速度が爆速なフレームワークです。
主な特徴としては
-
速さ: 非常にハイパフォーマンス。NodeJSやGoに匹敵する処理速度を誇る(StarletteとPydanticのおかげもあり)。Pythonの数あるフレームワークのうち最も処理速度が速いフレームワークの一つ。
-
コードの簡素化: コードの書く速度を約2~3倍から上昇させる。(*)
-
バグの少なさ: 約40%ほどの人為的コードバグを減らすことが可能。(*)
-
直感的に書ける: エディターのサポートも充実、補完も効きます。デバッグにかかる時間を減らすことが可能。
-
簡単: 簡単に書け、理解しやすいように設計されている。ドキュメント読むのに時間がたくさんかかる心配もない。
-
短い: コードの重複を避けることができる。渡す引数を変えるだけで様々な機能を提供する関数を備えている。
-
堅実: 本番環境でも開発環境と差異のないコードを使える。
-
Swaggerの提供: 作成したAPIはデフォルトで備えているSwaggerをもとに自動でドキュメント化され、各処理を実行できる。
(*)FastAPI制作チーム調べ、だそうです。
#とりあえずサーバーを立ち上げてみる
「コードは言葉より物を言う」と言うことで、早速使っていきたいと思います。
まずは適当にフォルダを作り
mkdir fastapi-practice
必要なパッケージをインストールします。
pip install fastapi sqlalchemy uvicorn mysqlclient
グローバルインストールが嫌な方はpoetryなどを使ってインストールしてください(このあとでどのみちpoetry使います)。
FastAPIを動かすために必要な以下のファイルを作成します
touch main.py
そして以下の様にコードを記述していきます。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def index():
return {'Hello': 'World'}
なんとこれだけでサーバーが立ち上がってしまいます。
uvicorn main:app
と打つだけでサーバーが立ち上がったはずです。
http://localhost:8000/
をブラウザで表示してみると{"Hello":"World"}
と表示されているはずです。
爆速ですね、FastAPI。
しかもSwaggerによりAPIの仕様も自動で作成されています!!(驚き!)
http://localhost:8000/docs
を表示してみてください。お洒落なUIで仕様書が作られているはずです。
タブを開いてTry it out
のボタンを押すと実際にリクエストを送り、レスポンスを確認することだってできてしまいます!(感動!!)
ちなみに
http://localhost:8000/redoc
も自動で作られておりさらに詳細なドキュメントを簡単に作ることもできてしまいます!(凄すぎ!!!)
実際に使ってみる (docker環境構築編)
それではリアルケースを想定したFastAPI + MySQLでRESTfulなAPIを作成してみましょう。
FastAPIはdockerで簡単に環境構築できるのでmysqlとFastAPIをそれぞれ今時っぽくコンテナ内で動かし、通信させる様にしてみます。
まずはフォルダ内にdocker-compose.ymlとdocker-sync.yml, Dockerfileを作成します。
touch docker-compose.yml docker-sync.yml Dockerfile
dockerの詳しい使い方の説明はここでは省きますが、Dockerfileにコンテナを作成するための情報、docker-compose.ymlに作成されたコンテナ上で走らせるコマンド、docker-sync.ymlでローカルの開発環境とdockerコンテナ内のファイルをリアルタイムで同期するためにコードをつらつらと書いていきます。
docker-syncの使い方は他の方が書いてくださった以下の様な記事が参考になるかと思うので読んでみてください。
docker-syncは使わなくてもできますが、同期速度を爆速!にするために私は使ってます。
それではDockerfileからコードを書いていきます。
FROM python:3.8-alpine
RUN apk --update-cache add python3-dev mariadb-dev gcc make build-base libffi-dev libressl-dev
WORKDIR /app
RUN pip install poetry
パッケージ管理にはpoetryを使います。
パッケージ管理にはpipenvやpyflowなどもあるのでここは好みですかね...?
https://qiita.com/sk217/items/43c994640f4843a18dbe
こちらの記事に各パッケージマネージャーが分かりやすくまとめられています。
気になる方は是非一読してみてください。
続いてdocker-sync.yml
version: "2"
options:
verbose: true
syncs:
fastapi-practice-sync:
src: "."
notify_terminal: true
sync_strategy: "native_osx"
sync_userid: "1000"
sync_excludes: [".git", ".gitignore", ".venv"]
そしてdocker-compose.ymlです
version: "3"
services:
db:
image: mysql:latest
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_DATABASE: fastapi_practice_development
MYSQL_USER: root
MYSQL_PASSWORD: "password"
MYSQL_ROOT_PASSWORD: "password"
ports:
- "3306:4306"
volumes:
- mysql_data:/var/lib/mysql
fastapi:
build:
context: .
dockerfile: "./Dockerfile"
command: sh -c "poetry install && poetry run uvicorn main:app --reload --host 0.0.0.0 --port 8000"
ports:
- "8000:8000"
depends_on:
- db
volumes:
- fastapi-sync:/app:nocopy
- poetry_data:/root/.cache/pypoetry/
volumes:
mysql_data:
poetry_data:
fastapi-sync:
external: true
docker-compose.ymlでミソなのが永続化するデータとその場かぎりのデータをうまく使い分けることです。
永続化しないデータはdocker-compose downするたびにリセットされてしまいます。
今回のケースだとmysql内のデータ、poetryでインストールしたパッケージは永続化させ、コンテナを立ち上げる度にmysql内のデータが空になったりパッケージをダウンロードしなくていい様にします。
また書いていくコードはdocker-syncを使って同期させたいのでfastapi-practice-sync:/app:nocopy
を記述して勝手に同期されるのを防ぎます。
MySQLもdockerから最新のイメージをpullして構築していきます。
ここまででdockerのセットアップは終了です。
#実際に使ってみる(FastAPI設定編)
まずはFastAPIに必要なパッケージをインストールするpoetryのセットアップです。
poetry init
をターミナルで叩きます。
そうすると対話形式でセットアップが始まりますのでyesかnoを連打しましょう。(基本デフォルトの設定で問題ないのでEnter連打でも問題ない...と思います)
そうするとpyproject.toml
というファイルが作成されたかと思います。
ここにパッケージの依存情報が追記されていくのでpoetryを使ってFastAPIを立ち上げるのに必要なパッケージをインストールしていきましょう。
poetry add fastapi sqlalchemy uvicorn mysqlclient
こちらを入力しパッケージのインストールが終わるのを待ちます。
終了したら、pyproject.toml
を開いてみるとインストールされたパッケージの情報が記載されていることがわかります。
[tool.poetry]
name = "fastapi-practice"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
fastapi = "^0.61.1"
sqlalchemy = "^1.3.19"
uvicorn = "^0.11.8"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
そうしたらあとはdocker-compose build
を打ち、イメージをビルドしてdocker-sync-stack start
を入力するだけです!
*docker-sync-stack start
はdocker-sync start
とdocker-compose up
を同時に実行するコマンドです。ログもよしなに出してくれるので便利です。
新たにパッケージをインストールする際は、まずはローカルでpoetry add
でパッケージをインストールしてdockerコンテナを再起動させればコンテナ内にも同期されるはずです!
マイグレーションをする
お次はDB(MySQL)と連携させていきます。
今回はCRUDの勉強といえばTodoリストの作成!なのでTodoテーブルを定義しマイグレーションをかけていくことにします。
Todos Table
|column |datatype
|---|---|---|
|id |integer
|title |string
|content |string
|done |boolean
この様な構成のテーブルをマイグレーションしていきます。
まずはデータベースを定義するファイルを作り
touch db.py
以下の内容を書き込んでいきます。
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
user_name = "root"
password = "password"
host = "db"
database_name = "fastapi_practice_development"
DATABASE = f'mysql://{user_name}:{password}@{host}/{database_name}'
engine = create_engine(
DATABASE,
encoding="utf-8",
echo=True
)
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(30), nullable=False)
content = Column(String(300), nullable=False)
done = Column(Boolean, default=False)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def main():
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
if __name__ == "__main__":
main()
FastAPIはsqlalchemyというPythonの中で最もよく利用されるORM(Object-Relation Mapping)の一つを使ってデータベースとPythonのオブジェクトを関連付けるのが主流みたいです。
これを書いたらdockerのコンテナ内に入り、マイグレーションをかけていきます。
docker-sync-stack start
でコンテナを立ち上げ同期モードにし、
docker container ls
で立ち上がっているコンテナリストをみます。
そしたら、
docker exec -it {コンテナ名} sh
を叩き、コンテナの中に入ります。
そして、以下のコマンドでマイグレーションをかけていきます。
poetry run python db.py
そうすると無事マイグレーションが走りテーブル作成に成功したのではないでしょうか!
そしたら次はCRUD処理をFastAPIで書いていきます。
FastAPIでCRUD処理を書こう
拡張性を意識してFastAPIに内蔵されているinclude_routerという機能を使い、ファイルを分割していきます。
mkdir routers
と打ち、
touch routers/todo.py
というファイルを作ります。
こちらにCRUD処理を書いていきます。
from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from starlette.requests import Request
from pydantic import BaseModel
from db import Todo, engine, get_db
router = APIRouter()
class TodoCreate(BaseModel):
title: str
content: str
done: bool
class TodoUpdate(BaseModel):
title: str
content: str
done: bool
@router.get("/")
def read_todos(db: Session = Depends(get_db)):
todos = db.query(Todo).all()
return todos
@router.get("/{todo_id}")
def read_todo_by_todo_id(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
return todo
@router.post("/")
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(title=todo.title,
content=todo.content, done=todo.done)
db.add(db_todo)
db.commit()
@router.put("/{todo_id}")
def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
db_todo.title = todo.title
db_todo.content = todo.content
db_todo.done = todo.done
db.commit()
@router.delete("/{todo_id}")
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
db.delete(db_todo)
db.commit()
この様にしてざっとCRUD操作を書いていきました。
@routerの後にリクエスト名を書き、動作対象のURLを書くだけです。
そしたらこちらを読み込める様にmain.py
も編集していきます。
from fastapi import FastAPI
from routers import todos
app = FastAPI()
app.include_router(
todos.router,
prefix="/todos",
tags=["todos"],
responses={404: {"description": "Not found"}},
)
prefixはurlのパスを作ってくれます。tagsはdocsを見やすい様グルーピング化してくれます。
そうして
http://localhost:8000/docs
に接続すると以下の様になっているはずです!
タブを開いてポチポチボタンを押してCRUD処理を試してみてください!
このままだとフロントエンドから呼ぶ際CORSエラーが起きるので別アプリから呼ぶ際は以下のCORSの処理を追記してみてください。
# 追記
from starlette.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#まとめ
FastAPIいかがだったでしょうか?
こんなにも少ないコード量でAPIが作成できてしまうのがとても魅力的ですね。
Pythonでマイクロサービスを作成する際とても相性が良さそうです。
Qiita初投稿だったため何か分かりづらい点あったら質問くださいませ!
これからはなるべくQiitaにもアウトプットしていきたい...です(頑張る