初めに
今回はFastAPIを使った簡単なRest APIのCRUDを作成してみました。
下記の要件定義をもとに作成してます。
要件定義
・フレームワークにFast APIを利用すること
・DBにMySQLを利用すること
・ORMにSQLAlchemyを利用すること
なお、作成いただくAPIのパスは /user とし、スキーマとしては以下を備えてください。ただし、各スキーマの制約等は不要とし、その他未指定の場合はご自身で前提条件を設定していただいて構いません。
・ユーザーID(主キー、更新不可)
・ユーザー名
・メールアドレス
使った環境
- Python 3.10.4 (Anaconda 3で環境構築)
- MySQL 5.7.29
- XAMPP 3.3.0
fastapi==0.85.0
# (PythonでREST APIを開発するためのWebフレームワーク)
PyMySQL==1.0.2
# (PythonからMySQLの操作が行えるパッケージ)
SQLAlchemy==1.4.41
# (後ほど説明)
uvicorn==0.18.3
# (Python用のASGI Webサーバー実装)
になります。
SQLAlchemy について
SQLAlchemyとは、Pythonの中では最もよく利用されているORMの一つです。ORM以外にも以下の機能を持ちます。
-
データベースへの接続、SQLの実行
SQLAlchemyは様々なデータベースに対して接続してSQLを実行することができます。 -
メタデータ
SQLAlchemyにはメタデータと呼ばれるテーブルとPythonのモデルクラスをマッピングする機能があります。 -
SQL Expression Language
JavaのORM、HibernateにはHQLというクエリ言語がありますが、SQLAlchemyにも同様にクエリを表す記述が用意されています。 -
ORM
クエリの実行結果をモデルに格納します。
MySQLの設定
今回はXAMPP
でもlocal
でも、全く同じ構造でデータベースとテーブルを作ってます。
local
のコマンドプロンプトで説明します。
※XAMPP側
XAMPPの方は、phpMyAdminが簡単にアクセス可能なので初めの環境では使わせていただきました。
テーブルのスキーマ
中のデータ
こちらにも仮で先にデータを入れてます。
ファイル構造
Rest_API_CRUD
|-- fast_api
| |-- db.py
| |-- models.py
| |-- routes.py
| |-- schemas.py
| |-- test.py
|
README.md
requirements.txt
コードの中身
コードをわかりやすく説明していきます。
db.py
コードの全体
from sqlalchemy import create_engine, MetaData
# XAMPP上での実行コマンド
# engine = create_engine("mysql+pymysql://root@localhost:3306/fastapi")
#ローカルでの実行コマンド
engine = create_engine("mysql+pymysql://root:{password}@localhost:3306/fastapi")
meta = MetaData()
conn = engine.connect()
SQLAlchemyを使ってMy SQLに接続していきます。
create_engine
の構成の説明
create_engine
で、DBエンジンのインスタンスを作成します。
engine = create_engine("{dialect}+{driver}://{username}:{password}@{host}:{port}/{database}")
要素 | 説明 |
---|---|
dialect | DBの種類 今回はMySQL |
driver | DBに接続するためのドライバー 今回はPyMySQL |
username | DBに接続することができるユーザ名 初期設定のままなので、 root
|
password | DBに接続するためのパスワードlocal のみ記載※XAMPPの場合、パスワードはなしです。 |
host | ホスト名(localhostとかIPアドレスとか) 今回は localhost
|
port | ポート番号 今回はデフォルトなので 3306 を使用 |
database | 接続するデータベース名 MySQLで確認した通り fastapi
|
その他
MetaData()
テーブルのスキーマをPythonのコード上で定義できる。
engine.connect()
DBに接続する。
models.py
全体のコード
from sqlalchemy import Table, Column
from sqlalchemy.sql.sqltypes import Integer, String
from db import meta
users = Table(
'fast',meta,
Column('id',Integer,primary_key=True),
Column('name',String(255)),
Column('mail',String(255)),
)
models.py
では、db.py
で作った変数meta
を使用して、テーブルのスキーマをPythonのコード上で定義していきます。
users = Table(
'{MyAQLで作成したテーブルの名前}',meta,
# Columnでカラムの定義
# 今回の主キーはidになるため、primary_key=Trueが設定されてる。
Column('id',Integer,primary_key=True),
Column('name',String(255)),
Column('mail',String(255)),
)
schemas.py
全体のコード
from pydantic import BaseModel
class User(BaseModel):
name: str
mail: str
ここではPydanticを使って型の変換をしてます。
今回は、id
が自動で割り振られるので、記入するname
とmail
を文字列として変換します。
routes.py
全体のコード
from fastapi import APIRouter
from db import conn
from models import users
from schemas import User
user = APIRouter(prefix='/user', tags=['user'])
# 全件表示
@user.get("/")
async def read_data():
return conn.execute(users.select()).fetchall()
# 指定のIDのみ検索
@user.get("/{id}")
async def read_data(id: int):
return conn.execute(users.select().where(users.c.id == id)).fetchall()
# 新しいデータの作成
@user.post("/")
async def write_data(user: User):
conn.execute(users.insert().values(
name=user.name,
mail=user.mail
))
return conn.execute(users.select()).fetchall()
# 既存データのアップデート
@user.put("/{id}")
async def update_data(id:int, user: User):
conn.execute(users.update().values(
name=user.name,
mail=user.mail
).where(users.c.id == id))
return conn.execute(users.select()).fetchall()
# データの削除
@user.delete("/{id}")
async def delete_data(id: int):
conn.execute(users.delete().where(users.c.id == id))
return conn.execute(users.select()).fetchall()
Router
のインスタンス化
user = APIRouter(prefix='/user', tags=['user'])
prefix
で、これから作成するエンドポイントの頭のURLを指定。
tags
で、http;//127.0.0.1/docs
の分類で使用される。
routingの定義
@{APIRouterの変数}.{operation}({Path})
でroutingを定義してます。
async def
について
今回は全体的にasync def
と言われる、非同期処理で行っていきます。
conn.execute
について
db.py
で作成した変数conn
を使って処理をしています。
.execute
を使ってSQLを実行できます。
全件表示の処理
@user.get("/")
async def read_data():
return conn.execute(users.select()).fetchall()
全件表示の返り値には、users.select()
でuser
の中身を取り出して、.fetchall()
で返り値を全部表示しています。
※一件だけにしたい場合は、.fetchone()
で行える。
指定のIDのみ検索
@user.get("/{id}")
async def read_data(id: int):
return conn.execute(users.select().where(users.c.id == id)).fetchall()
routingのPathにid
を追加して、id
に該当するデータを持ってくるようにします。
id
で探すために.where(users.c.id == id)
としており、users
のカラムのid
から、Pathで指定したid
と同じものを探すというものになります。
新しいデータの作成
@user.post("/")
async def write_data(user: User):
conn.execute(users.insert().values(
name=user.name,
mail=user.mail
))
return conn.execute(users.select()).fetchall()
データの作成には、.execute
内でinsert文を記入します。
作成に必要なデータは、name
とmail
です。(id
は自動で作られます。)
最後に返り値でinsertした後のデータを呼び出してます。
既存データのアップデート
@user.put("/{id}")
async def update_data(id:int, user: User):
conn.execute(users.update().values(
name=user.name,
mail=user.mail
).where(users.c.id == id))
return conn.execute(users.select()).fetchall()
データのアップデートには、.execute
内でupdate文を記入します。
基本的には、新しいデータの作成と同じですが、id
の指定がPathにあります。
そのため、同じid
のデータを書き換えるという処理になります。
こちらも、最後に返り値で全体を出力してます。
データの削除
@user.delete("/{id}")
async def delete_data(id: int):
conn.execute(users.delete().where(users.c.id == id))
return conn.execute(users.select()).fetchall()
データの削除では、.execute
内でdelete文を記入します。
今回はPathで指定したid
と同じものを削除するというものになります。
こちらも、最後に返り値で全体を出力してます。
test.py
全体のコード
from fastapi import FastAPI
from routes import user
app = FastAPI()
app.include_router(user)
app
でUvicornで呼び出せるように指定しています。
app.include_router(user)
で、サブモジュールroutes
のuser
をinclude関数で呼び出しています。
実行画面
それでは、実行していきます。
概要
FastAPIでは、OpenAPIを自動生成してくれます。
URL:http://127.0.0.1:8000/docs#/
にアクセスすると入れます。
画面はこのようにまとまっており、ここで実行から確認までできます。
全件表示
新しいデータの作成
name
とmail
を書き込むことで反映されます。
実行すると、新しくデータが作成されます。
(自分の方で何回か作成したため、id
が10になってますが、実際は連番で登録されます。削除したid
は欠番になります。)
指定のIDのみ検索
既存データのアップデート
id
を指定することで、該当データを書き換えることができます。
name
とmail
を書き込むことで反映されます。
しっかり反映されてます。
データの削除
id
を指定することで、該当データを削除することができます。
削除されました。
次作成されるデータは11番からになります。
最後に
調べながらやりましたが、個人的にはこれをReactまでやりたいなと思ったので、余裕があれば自分でやってみたいと思います。
とても楽しかったです。
GitHubにも上げてるので見てみてください。
https://github.com/kyoppy-1229/Rest_API_CRUD
参考動画