iMind Developers Blog

iMind開発者ブログ

Flask + SQLAlchemyでDBの操作

概要

Flask + SQLAlchemyを利用してテーブル追加・削除、レコードの登録・更新・削除、検索、リレーションを利用した検索を実行する。

データベースはSQLite使用。

バージョン情報

  • Flask==1.1.1
  • Flask-SQLAlchemy==2.4.1

ファイル構成

サンプルコードは下記のような構成で作成します。

├── app.py
├── example.db
└── models
    ├── child.py
    └── example.py

example.dbはSQLiteのファイルです。

modelsに入っているexample.pyが親テーブルで、child.pyはそれに紐づく子テーブルを実装します。

dbの指定

下記のような記述で app.py というファイルを作成します。

import flask
import flask_sqlalchemy

app = flask.Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = True

db = flask_sqlalchemy.SQLAlchemy(app)

if __name__ == '__main__':
    app.run()

これを実行すると example.db というファイル名でSQLIteのDBファイルが生成されます。

SQLALCHEMY_ECHOをTrueに設定すると実行されるSQLが標準出力されます。

モデルの生成

models/example.py に sqlalchemy のモデルファイルを作成します。

import datetime

from app import db

class Example(db.Model):
    id    = db.Column(db.Integer, primary_key=True)
    name  = db.Column(db.String, nullable=False)
    age   = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

app.pyで記述したdbを参照しています。

id, name age, created_at, updated_atの5つのカラムを持つテーブルを定義しています。

テーブルの生成

テーブルの生成は指定のモデルをimportした後、db.create_all()することで実行できます。

from models.example import Example 
from app import db
from models.child import Child 

db.create_all()

実行されるSQL

CREATE TABLE example (
    id INTEGER NOT NULL, 
    name VARCHAR NOT NULL, 
    age INTEGER, 
    created_at DATETIME, 
    updated_at DATETIME, 
    PRIMARY KEY (id)
)

sqlite3のコマンドから生成されたDBを確認してみます。

$ sqlite3 example.db 

> .tables
example

> .schema
CREATE TABLE example (
    id INTEGER NOT NULL, 
    name VARCHAR NOT NULL, 
    age INTEGER, 
    created_at DATETIME, 
    updated_at DATETIME, 
    PRIMARY KEY (id)
);

INSERT

db.session.addでデータを追加します。

from models.example import Example 

from app import db

example = Example()
example.id = 1
example.name = 'foo'
example.age = 100

db.session.add(example)
db.session.commit()

実行されるSQL

BEGIN (implicit)
INSERT INTO example (id, name, age, created_at, updated_at) VALUES (?, ?, ?, ?, ?)
  -- (1, 'foo', 100, '2020-02-03 18:07:34.819812', '2020-02-03 18:07:34.819825')
COMMIT

DELETE

モデルを指定してDELETEする場合の記述。

db.session.delete(example)
db.session.commit()

実行されるSQL

 DELETE FROM example WHERE example.id = ?
  -- (1,)
COMMIT

条件に合致するレコードをDELETEする場合。

Example.query.filter_by(name = 'foo').delete()
db.session.commit()

実行されるSQL

BEGIN (implicit)
DELETE FROM example WHERE example.name = ?
  -- ('foo',)
COMMIT

UPDATE

updateはモデルの内容を書き換えてコミットすると反映されます。

# 更新対象の行を取ってくる
row = Example.query.get(1)

# update
row.name = 'bar'
db.session.commit()

実行されるSQL

BEGIN (implicit)
SELECT example.id AS example_id, example.name AS example_name, example.age AS example_age, example.created_at AS example_created_at, example.updated_at AS example_updated_at
FROM example 
WHERE example.id = ?
  --  (1,)
UPDATE example SET name=?, updated_at=? WHERE example.id = ?
  -- ('bar', '2020-02-03 18:09:53.557883', 1)
COMMIT

1レコードの更新ではなく、条件に合致した複数行を更新する場合。

Example.query.filter(Example.age > 10).update({'name': 'new_name'})
db.session.commit()

実行されるSQL(2レコードを投入して実行)

BEGIN (implicit)
UPDATE example SET name=?, updated_at=? WHERE example.age > ?
  -- ('new_name', '2020-02-03 18:21:07.597725', 10)
COMMIT

検索

IDで検索

row = Example.query.get(1)

実行されるSQL

BEGIN (implicit)
ELECT example.id AS example_id, example.name AS example_name, example.age AS example_age, example.created_at AS example_created_at, example.updated_at AS example_updated_at 
FROM example 
WHERE example.id = ?
  -- (1,)

条件検索して first() で1行目を取得

row = Example.query.filter_by(name='bar').first()

実行されるSQL

SELECT example.id AS example_id, example.name AS example_name, example.age AS example_age, example.created_at AS example_created_at, example.updated_at AS example_updated_at 
FROM example 
WHERE example.name = ?
 LIMIT ? OFFSET ?
  -- ('bar', 1, 0)

条件に合致するレコードをループ

for row in Example.query.filter(Example.age > 10):
    print(row)

relationの設定

example.pyとchild.pyの間に1対多のリレーションを貼ってみます。

child.pyの記述

import datetime

from app import db

class Child(db.Model):
    id    = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name  = db.Column(db.String, nullable=False)
    example_id = db.Column(db.Integer, db.ForeignKey('example.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

childのexample_idカラムに exampleテーブルのidカラムに対してForeignKeyが設定されています。

example.py側にchildへの参照を記述します。

import datetime

from app import db

class Example(db.Model):
    id    = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name  = db.Column(db.String, nullable=False)
    age   = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

    childs = db.relationship('Child')

これで Exampleクラスのchildsにアクセスすればchildの値が取れます。

1度テーブルを再生成してすっきりさせます。

from app import db
from models.example import Example
from models.child import Child 

db.drop_all()
db.create_all()

テストデータを入れます。

example = Example()
example.id = 1
example.name = 'foo'
example.age = 100
db.session.add(example)

child = Child()
child.id = 1
child.name = 'chid1'
child.example_id = 1
db.session.add(child)

child = Child()
child.id = 2
child.name = 'chid2'
child.example_id = 1
db.session.add(child)

db.session.add(example)
db.session.commit()

Exampleからchildsを取得してみます。

childs = Example.query.get(1).childs
for child in childs:
    print(child)

    #=> <Child 1>
    #=> <Child 2>

ちゃんとexample.idに紐づくレコード2件が取れました。

実行されるSQL

SELECT example.id AS example_id, example.name AS example_name, example.age AS example_age, example.created_at AS example_created_at, example.updated_at AS example_updated_at 
FROM example 
WHERE example.id = ?
  -- (1,)

SELECT child.id AS child_id, child.name AS child_name, child.example_id AS child_example_id, child.created_at AS child_created_at, child.updated_at AS child_updated_at 
FROM child 
WHERE ? = child.example_id
  -- (1,)

app.pyへの参照を避ける

本例ではコードをシンプルにする為にapp.pyのdb変数に対してアクセスしていますが、実際に開発する際はそれは避けて下記のようなパターンを用います。

https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/

改定履歴

Author: Masato Watanabe, Date: 2020-02-03, 記事投稿