1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FastAPIを使ったCRUD操作(MySQL、SQLAlchemy、XAMPP)

Last updated at Posted at 2022-10-06

初めに

今回は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
requirements.txt
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のコマンドプロンプトで説明します。

fastapiが今回作成したデータベースになります。
image.png

テーブル名はfastになります。
image.png

テーブルのスキーマ
image.png

仮でこのようなデータを一つ入れてます。
image.png

※XAMPP側
XAMPPの方は、phpMyAdminが簡単にアクセス可能なので初めの環境では使わせていただきました。

テーブルのスキーマ
image.png
中のデータ
こちらにも仮で先にデータを入れてます。
image.png

ファイル構造

  Rest_API_CRUD
  |-- fast_api
  |   |-- db.py
  |   |-- models.py
  |   |-- routes.py
  |   |-- schemas.py
  |   |-- test.py
  |
  README.md
  requirements.txt

コードの中身

コードをわかりやすく説明していきます。

db.py

コードの全体

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

全体のコード

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

全体のコード

schemas.py
from pydantic import BaseModel

class User(BaseModel):
    name: str
    mail: str

ここではPydanticを使って型の変換をしてます。
今回は、idが自動で割り振られるので、記入するnamemailを文字列として変換します。

routes.py

全体のコード

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文を記入します。
作成に必要なデータは、namemailです。(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

全体のコード

test.py
from fastapi import FastAPI
from routes import user

app = FastAPI()

app.include_router(user)

appでUvicornで呼び出せるように指定しています。
app.include_router(user)で、サブモジュールroutesuserをinclude関数で呼び出しています。

実行画面

それでは、実行していきます。

概要

FastAPIでは、OpenAPIを自動生成してくれます。
URL:http://127.0.0.1:8000/docs#/
にアクセスすると入れます。
image.png
画面はこのようにまとまっており、ここで実行から確認までできます。

全件表示

image.png

新しいデータの作成

namemailを書き込むことで反映されます。
image.png
実行すると、新しくデータが作成されます。
(自分の方で何回か作成したため、idが10になってますが、実際は連番で登録されます。削除したidは欠番になります。)
image.png

指定のIDのみ検索

idを指定することで、該当データのみ抽出できます。
image.png

既存データのアップデート

idを指定することで、該当データを書き換えることができます。
namemailを書き込むことで反映されます。
image.png
しっかり反映されてます。
image.png

データの削除

idを指定することで、該当データを削除することができます。
image.png
削除されました。
次作成されるデータは11番からになります。
image.png

最後に

調べながらやりましたが、個人的にはこれをReactまでやりたいなと思ったので、余裕があれば自分でやってみたいと思います。
とても楽しかったです。
GitHubにも上げてるので見てみてください。
https://github.com/kyoppy-1229/Rest_API_CRUD

参考動画

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?