この記事では、爆速でAPIを実装したい。また、FastAPIの文献少なすぎて、
MySQLとの連携ってどうやってやるの?と疑問に思っている方に向けて、
三分で環境を構築する方法を紹介しています。
前提
- MySQLを触ったことがある。selectやwhereなどがわかる
- Pythonを少しは触ったことがある。
- Dockerをインストールして使える状態
※ まだ、Dockerをインストールしていない方は、以下のサイトでわかりやすく紹介しているので、
参考にしながら導入してみましょう!
https://qiita.com/scrummasudar/items/750aa52f4e0e747eed68
FastAPIとは???
Pythonを使ったAPI構築のためのフレームワークです。ちなみに、Python3.6以上で動作します。
Pythonを使ったAPIはFlaskやdjangoなどが有名で、どれも簡単にAPIを構築することができますが、
FastAPIでは、より手軽に、そして動作も速く、非同期通信も簡単に実装することができます。
公式では、NodeJSやGoと同等の高いパフォーマンスとうたっています。
公式にはサンプルも多いので、困ったときはこちらを確認してみるといいでしょう。
https://fastapi.tiangolo.com/
今回やること
- docker-composeでMySQLとFastAPIを起動する
- 起動したMySQLに接続し、設定ファイルどおりに構築されているか確認する
- FastAPIで実装したAPIをSwaggerやcurlで叩いてMySQL上のデータを操作してみる
できたものがこちらになります(お急ぎの方)
今回紹介するコードの完成版はこちらになります
https://github.com/hogeline/sample_fastapi
お急ぎの方はこちらからすぐ環境を構築することができます
はい、三分で実装できましたね笑
お急ぎ出ない方は、以下から本題です。
事前準備
ディレクトリ構造
.
├── code
│ ├── db.py
│ ├── main.py
│ └── model.py
├── docker
│ ├── api
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ └── mysql
│ ├── Dockerfile
│ ├── conf.d
│ │ └── my.cnf
│ └── initdb.d
│ ├── schema.sql
│ └── testdata.sql
└── docker-compose.yml
今回のディレクトリ構造はこのようになっています
codeにはPythonのコードが入ります
dockerにはDockerfileやMySQLの設定、
FastAPIを実行するために必要なパッケージなどの情報が入っています。
###1. docker-compose.ymlの作成
version: "3.3"
services:
# MySQL
db:
container_name: "db"
# path配下のDockerfile読み込み
build: ./docker/mysql
# コンテナが落ちたら再起動する
restart: always
tty: true
environment:
MYSQL_DATABASE: sample_db
MYSQL_USER: user
MYSQL_PASSWORD: password # ユーザのパスワード
MYSQL_ROOT_PASSWORD: password # ルートパスワード
ports:
- "3306:3306"
volumes:
- ./docker/mysql/initdb.d:/docker-entrypoint-initdb.d # 定義どおりにテーブル作成
- ./docker/mysql/conf.d:/etc/mysql/conf.d # MySQLの基本設定(文字化け対策)
- ./log/mysql:/var/log/mysql # ログの保存
networks:
- local-net
# FastAPI
api:
# db起動後に立ち上げる
links:
- db
container_name: "api"
# path配下のDockerfile読み込み
build: ./docker/api
ports:
- "8000:8000"
volumes:
- ./code:/usr/src/server
networks:
- local-net
# コンテナ間で通信を行うためのネットワークブリッジ
networks:
local-net:
driver: bridge
docker-composeでは、MySQLとFastAPIを扱うdbとapiのサービスを作ります
それぞれのコンテナイメージは./docker/mysqlと./docker/apiのDockerfileを参照して作成します
apiからdbのmysqlへ接続するためにコンテナ間の通信が必要だったので、networks: local-netを定義しています
###2. Dockerfileを作成する
FROM mysql:5.7
# MySQLの操作ログのファイルを作成
RUN touch /var/log/mysql/mysqld.log
FROM python:3.7
WORKDIR /usr/src/server
ADD requirements.txt .
# requirements.txtにリストされたパッケージをインストールする
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# コンテナ起動後、FastAPIを実行し8000ポートで待機
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
今回利用するPythonのバージョンは3.7です
FastAPIを立ち上げるuvicornのオプションに--reloadをつけると
./code/main.pyを編集と同時に変更内容が反映されます
###3. pipでインストールするパッケージ一覧
mysqlclient
sqlalchemy
uvicorn
fastapi
mysqlclientとsqlalchemyはMySQLを操作するために必要で、
FastAPIを起動するために必要なパッケージはuvicornとfastapiのみです
###4. MySQL設定ファイルの作成
[mysqld]
character-set-server=utf8 # mysqlサーバー側が使用する文字コード
skip-character-set-client-handshake # 文字化け対策
default-storage-engine=INNODB # InnoDBを使用
explicit-defaults-for-timestamp=1 # テーブルにTimeStamp型のカラム用
general-log=1 # 実行したクエリの全ての履歴を記録
general-log-file=/var/log/mysql/mysqld.log # ログの出力先
[mysqldump]
default-character-set=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8 # mysqlのクライアント側が使用する文字コード
sqlalchemyを利用するにあたって、ストレージエンジンはInnoDBじゃないといけません、MySQLではデフォルトでInnoDBを利用しますが、一応指定しましょう
###5. スキーマの定義
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
age INT,
PRIMARY KEY (id)
);
スキーマで定義したuserテーブルを作成します
###6. テストデータの作成
INSERT INTO user (name, age) VALUES ("太郎", 15);
INSERT INTO user (name, age) VALUES ("次郎", 18);
INSERT INTO user (name, age) VALUES ("花子", 20);
テーブル作成後、上記のSQLを実行しテストデータを流します
###7. PythonでDBと接続するための設定
# -*- coding: utf-8 -*-
# DBへの接続設定
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
# 接続したいDBの基本情報を設定
user_name = "user"
password = "password"
host = "db" # docker-composeで定義したMySQLのサービス名
database_name = "sample_db"
DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
user_name,
password,
host,
database_name,
)
# DBとの接続
ENGINE = create_engine(
DATABASE,
encoding="utf-8",
echo=True
)
# Sessionの作成
session = scoped_session(
# ORM実行時の設定。自動コミットするか、自動反映するか
sessionmaker(
autocommit=False,
autoflush=False,
bind=ENGINE
)
)
# modelで使用する
Base = declarative_base()
# DB接続用のセッションクラス、インスタンスが作成されると接続する
Base.query = session.query_property()
既存のDBを利用したい場合は、このファイルの基本情報を設定している項目を編集することで、
そちらへアクセスすることが可能です
###8. FastAPIで扱うモデルを定義する
# -*- coding: utf-8 -*-
# モデルの定義
from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
from db import Base
from db import ENGINE
# userテーブルのモデルUserTableを定義
class UserTable(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(30), nullable=False)
age = Column(Integer)
# POSTやPUTのとき受け取るRequest Bodyのモデルを定義
class User(BaseModel):
id: int
name: str
age: int
def main():
# テーブルが存在しなければ、テーブルを作成
Base.metadata.create_all(bind=ENGINE)
if __name__ == "__main__":
main()
ここでは、FastAPIで操作するMySQLのテーブルのモデルの定義と
POSTやPUTで送られてきたリクエストbodyを扱うためにモデルを定義しています
FastAPIの実装
FastAPIの実装を行います
今回は、GETとPOST、PUTを実装しました
from fastapi import FastAPI
from typing import List # ネストされたBodyを定義するために必要
from starlette.middleware.cors import CORSMiddleware # CORSを回避するために必要
from db import session # DBと接続するためのセッション
from model import UserTable, User # 今回使うモデルをインポート
app = FastAPI()
# CORSを回避するために設定
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ----------APIの実装------------
# テーブルにいる全ユーザ情報を取得 GET
@app.get("/users")
def read_users():
users = session.query(UserTable).all()
return users
# idにマッチするユーザ情報を取得 GET
@app.get("/users/{user_id}")
def read_user(user_id: int):
user = session.query(UserTable).\
filter(UserTable.id == user_id).first()
return user
# ユーザ情報を登録 POST
@app.post("/user")
# クエリでnameとstrを受け取る
# /user?name="三郎"&age=10
async def create_user(name: str, age: int):
user = UserTable()
user.name = name
user.age = age
session.add(user)
session.commit()
# 複数のユーザ情報を更新 PUT
@app.put("/users")
# modelで定義したUserモデルのリクエストbodyをリストに入れた形で受け取る
# users=[{"id": 1, "name": "一郎", "age": 16},{"id": 2, "name": "二郎", "age": 20}]
async def update_users(users: List[User]):
for new_user in users:
user = session.query(UserTable).\
filter(UserTable.id == new_user.id).first()
user.name = new_user.name
user.age = new_user.age
session.commit()
asyncをdefの前につけることで非同期で処理してくれます
defで定義した関数の引数にクエリやリクエストbodyが入ってきます。
例えば、
@app.get("/users/{user_id}")
def read_user(user_id: int):
であれば、"/users/1"のアクセスがあればuser_idは1の値が引数に入ってきます
ここらへんの使い方は、以下のサイトで詳しく紹介しているので参考にしてください
https://qiita.com/mtitg/items/47770e9a562dd150631d
クエリやレスポンスbodyから取得した値を使ってDBを操作します
操作はsqlalchemyを使って行うので、直感的に簡単にできると思います
詳しい使い方は、こちらのサイトで紹介されているので参考にしましょう
https://qiita.com/bokotomo/items/a762b1bc0f192a55eae8
実行手順
さっそく、FastAPIとMySQLを構築してAPIを叩いて見ましょう
###1. docker-composeコマンドを実行してみる
コンテナの作成&起動
$ docker-compose up -d --build
コンテナのステータスを確認
$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------------
api uvicorn main:app --reload ... Up 0.0.0.0:8000->8000/tcp
db docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp, 33060/tcp
apiとdbがUPしていることを確認しましょう
###2. MySQLへ接続して、設定どおりテーブルが作成されているか確認する
Dockerで作成したMySQLへ接続してみます.
このとき、ホストはlocalhostではなく127.0.0.1であることに注意です
$ mysql -u user -h 127.0.0.1 -D sample_db -p
Enter password:
mysql> show tables;
+---------------------+
| Tables_in_sample_db |
+---------------------+
| user |
+---------------------+
1 row in set (0.01 sec)
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 太郎 | 15 |
| 2 | 次郎 | 18 |
| 3 | 花子 | 20 |
+----+--------+------+
3 rows in set (0.00 sec)
userテーブルが作成していることが確認できました
###3. FastAPIのドキュメントを確認する
FastAPIでは、作成したAPIの仕様を自動でドキュメント化してくれます
ドキュメントはSwaggerというもので作られていて、ドキュメント内でAPIを叩いたりクエリを確認したりできます
上記のリンクへアクセスすると実装したAPIが表示されていると思います
###4. Swagger上でAPIを叩いてみる
GETで全ユーザ情報を取得
POSTでユーザ登録
Swaggerでは簡単にPOSTを試すことができます
curlだとヘッダーつけなきゃいけなくて大変ですが...
今回はnameに"四郎"、ageに10を入れてPOSTしてみます
クエリとしては/user?name="四郎"&age=10でPOSTします
Excuteを押したあと、200で返ってきたことを確認し
ユーザが登録されているか先程実行したGET /usersを再度実行してみましょう
増えていることが確認できました
PUTでユーザの情報を更新
今回、PUTではUserモデルをネストしたリクエストbodyを受け付けているので
[
{
"id": 2,
"name": "次郎",
"age": 13
},
{
"id": 3,
"name": "花子",
"age": 14
}
]
このようなbody作ってPUTしてみます
200で返ってきてることを確認し、
GET /usersで反映されているか確認してみましょう
###5. curlでAPIを叩いてみる
$ curl localhost:8000/users/1
{"id":1,"age":15,"name":"太郎"}
curlでuserテーブルのID1の情報が取得できました
感想
FastAPIは簡単にAPIを実装できて非同期処理も簡単にできるので便利だな〜と感じました
少しでも、このサンプルプログラムが役にたったな〜と思った方は
いいね!を押していただければと思います!
リファレンス
DockerComposeを用いたMySQLの環境構築はこちらのサイトを参考にしました
sqlalchemyはこちらのサイトを参考にしました
FastAPIの基本的な使い方を日本語でまとめてくれているサイト
公式ドキュメント