この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ORMとは
ORM(Object-Relational Mapping)とは、データベースとオブジェクト指向プログラミング言語とのマッピングを行うことを指します。
ただこれだけ聞くと、イメージしづらいですが、簡単に言うと、SQL文を直接記述せずに、通常のオブジェクトを扱うようにデータベースを扱うことができるということです。
Pythonでは、ORMモジュールの1つに SQLAlchemy があります。
SQLAlchemy
SQLAlchemyは、Pythonでポピュラーに使用されているORMライブラリの1つのようです。
SQLite、Postgresql、MySQL、Oracleなどさまざまなエンジンにも対応しており、PythonでORM、DBの処理を行うならSQLAlchemyを第一候補で考えれば良さそうです。
今回は、Flaskを用いてAPIとしてDBの情報を扱いたいので、FlaskにSQLAlchemyの機能を追加したFlask SQLAlchemyを使用します。
インストール
FlaskでREST APIを作成するので、Flask-RESTfulと取得した情報を扱いやすいようにflask-marshmallow もインストールします。
$ pip install Flask, Flask-RESTful, Flask-SQLAlchemy, flask-marshmallow
DBの準備
Dockerでmaridbを立ち上げます。
下記の内容でファイルを準備し、起動させます。
docker-compose.yml
version: "3"
services:
db:
image: mariadb
restart: always
ports:
- 3306:3306
command: --port 3306
environment:
- MYSQL_ROOT_PASSWORD=hogehoge.morita
- MYSQL_DATABASE=flask_test
mariadbの起動
$ docker-compose up -d
Flask側でのDB設定
インスタンスを生成し、初期化を行う関数を定義します。
この関数は、app.pyから実行されます。
database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def init_db(app):
db.init_app(app)
続いて、初期データを予め投入したいので、関数を準備します。
lib/create_init.py
from models.user import User
from database import db
def create_init(app):
with app.app_context():
db.drop_all()
db.create_all()
user_create()
db.session.commit()
def user_create():
# テストデータ
from test.data.user import users
for i in users:
u = User(name=i["name"])
db.session.add(u)
テストデータは、test/data/user.pyの中に記述します。
test/data/user.py
users = [
{ "name" : "山田太郎"},
{ "name" : "上田二郎"},
{ "name" : "田中三郎"}
]
DBの接続情報はconfig.py
で定義します。
接続情報は環境変数から読み出すようにしていますので.env
の中に記述します。
config.py
class DevConfig:
# SQLAlchemy
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{dbname}?charset=utf8'.format(**{
'user': os.getenv('DB_USER', 'root'),
'password': os.getenv('DB_PASSWORD', ''),
'host': os.getenv('DB_HOST', 'localhost'),
'dbname': os.getenv('DB_NAME')
})
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
Config = DevConfig
.env
DB_USER="root"
DB_PASSWORD="hogehoge.morita"
DB_HOST="localhost"
DB_NAME="flask_test"
ORMとして扱うモデルを定義
続いて、ORMとして扱うモデルを定義します。先程、定義したdb
が必要なので、database.py
からimportします。
また、Marshmallowを用いることで、出力フォーマットが簡単に指定ができますので、こちらも定義します。
REST API用のクラスUserapi
もここで定義します。
models/user.py
from flask import jsonify, abort, request
from flask_restful import Resource
from datetime import datetime
from database import db
from app import ma
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
device_id = db.Column(db.String(255), nullable=False, default="hoge")
name = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
class UserSchema(ma.Schema):
class Meta:
# Fields to expose
fields = ("id", "name", "created_at")
users_schema = UserSchema(many=True)
class Userapi(Resource):
def get(self):
"""
ユーザを1件取得
"""
id = request.args.get('id')
result = User.query.filter_by(id=id).all()
if len(result) != 0:
return jsonify({"users" : users_schema.dump(result)})
else:
abort(404)
def post(self):
"""
ユーザ登録
"""
#ユーザを追加
print(request.json)
users = request.json["users"]
for i in users:
u = User(name=i["name"])
db.session.add(u)
db.session.commit()
return '', 204
def put(self):
"""
ユーザ更新
"""
id = request.json["id"]
user = User.query.filter_by(id=id).first()
if user :
for key, val in request.json.items():
setattr(user, key, val)
db.session.commit()
else:
abort(404)
return '', 204
def delete(self):
"""
ユーザ削除
"""
id = request.args.get('id')
user = User.query.filter_by(id=id).first()
if user :
db.session.delete(user)
db.session.commit()
return '', 204
else:
abort(404)
app.pyを作成
では、定義したDBの初期化関数、データの投入する関数を実行し、Flaskでサーバを起動させます。
app.py
# coding: utf-8
from flask import Flask
from flask_restful import Api
from flask_marshmallow import Marshmallow
from database import init_db
from lib.create_init import create_init
from models.user import Userapi
app = Flask(__name__)
app.config.from_object('config.Config')
# DB init
init_db(app)
api = Api(app)
ma = Marshmallow(app)
create_init(app)
# API add
api.add_resource(Userapi, '/user')
if __name__ == "__main__":
app.run(debug=True)
$ flask run
動作確認
正しく動作しているかを確認します。
GETメソッドでユーザ情報1件取得してみます。
user_test.py
import requests
URL = "http://127.0.0.1:5000/"
# GET test
id=1
res = requests.get(URL+"user?id={}".format(id))
print(res.json())
すると、確かにidが1のユーザ情報が取得できていることが確認できます。
{'users': [{'created_at': '2021-10-21T09:45:32', 'id': 1, 'name': '山田太郎'}]}
最後に
Flask-SQLAlchemyを用いて簡易的ですが、ORMを行ってみました。SQL文を記述しなくても良いのはとても便利ですね。
一方で、内部処理がブラックボックスとなっているので、SQLのチューニングする際などにはあまり向いていないようです。その際は従来通りSQL文で記述したほうが良さそうです。
ただ、個人使用であれば、オブジェクトとしてDBを扱えるのはとても便利ですので、今後も使用していきたいと思います。