VS CodeとFlask-SQLAlchemyでデータベース操作:Visual Studio Codeで始めるPythonプログラミング(1/2 ページ)
Flask用のSQLAlchemy拡張機能「Flask-SQLAlchemy」の使い方を概観し、ToDoリストをデータベースに保存してみよう。
連載「Visual Studio Codeで始めるPythonプログラミング」
前回は簡単なToDoリストアプリを作りながら、Flaskを利用したWebアプリ開発で使用するいろいろな事項(Flaskが提供する各種メソッドやオブジェクト、Jinja2テンプレート、静的ファイルの扱いなど)を見た。今回はFlask-SQLAlchemyを利用して、このToDoリストアプリにデータベースを組み込んでみよう。
ただし、その前に「最小限のFlask-SQLAlchemyアプリ」を作り、それを使って、データベースの作成と操作を行う方法を見ていくことにする。
Flask-SQLAlchemyの基礎知識
SQLAlchemyはPython用の著名なSQLツールキット/ORMの1つであり、これをFlaskで利用するための拡張機能としてFlask-SQLAlchemyがある。まずはFlask SQLAlchemyを使って、データベースを利用する方法を見てみよう。なお、ここではデータベースとしてはお手軽に試せる(Pythonには標準でsqlite3モジュールが含まれているので)SQLite3を例に取る。
また、以下ではVS Code内で仮想環境「myenv」を作成し、そこにFlask(バージョン1.0.2)とFlask-SQLAlchemy(バージョン2.3.2)をインストールしている(「pip install flask-sqlclchemy」コマンドで両者をインストールできる)。環境としてはWindows版のVS Code 1.25.1で動作を検証している。
最小限のアプリ
FlaskアプリからFlask-SQLAlchemyを利用するには、前回までに見た手順でFlaskアプリのオブジェクトを構築した上で、Flask-SQLAlchemyが提供するSQLAlchemyクラスにそのアプリオブジェクトを渡してやる。これを行うコードが以下になる(app.pyファイル)。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db/sample.db"
db = SQLAlchemy(app)
Flask-SQLAlchemyでは使用するデータベースをFlaskアプリの構成情報「SQLALCHEMY_DATABASE_URI」で指定する必要がある。また、その指定には上でも見たようにURIを使用する。ここでは、「sqlite:///db/sample.db」としているが、これはSQLiteを使用する例となる。これは「プロジェクトフォルダにあるdbサブフォルダの下にあるsample.dbファイル」をデータベースとして利用することを意味する。Flask-SQLAlchemyは他にも多くのデータベースをサポートしており、一般的には「dialect+driver://<ユーザー名>:<パスワード>@<ホスト>:<ポート>/<データベース名>」のようになる(例:mysql://someuser:somepasswd@localhost/mydatabase)。具体的には「Connection URI Format」を参照されたい。
app.configオブジェクトは辞書形式で構成情報を保存/取得することが可能で、Flask-SQLAlchemyでは「SQLALCHEMY_DATABASE_URI」の他にも幾つかのキーがサポートされている。これらの中でよく目にすると思われるのは「SQLALCHEMY_TRACK_MODIFICATIONS」だ。これをTrueにすると、Flask-SQLAlchemyがデータベースの変更を追跡管理して、シグナルを発生するようになる。ただし、これを有効にすると追加のメモリが必要となる。デフォルト値はNoneで、この場合、追跡管理を有効にする一方で「将来的にはデフォルトで無効化される」という警告を(Flaskアプリ起動時に)表示するようになっている。他の構成キーについては「Configuration Keys」を参照されたい。
appオブジェクトに構成情報を設定したら、後はそれをSQLAlchemyコンストラクタに渡すだけだ。ただし、ここではデータベースをそもそも作成しておらず、そこに保存するテーブルももちろん作成できていない。だが、これだけでもアプリとしては一応機能する。そこで仮想環境を有効にしたターミナルを開き、「flask shell」コマンドを実行して、試してみよう。「flask shell」コマンドは、環境変数FLASK_APPで指定したアプリのコンテキストで動作するPython対話環境を起動するコマンドだ(環境変数FLASK_APPの設定方法はターミナルの種類による)。以下にコマンドプロンプトをベースとしたターミナルで「flask shell」コマンドを実行したところを示す。
上の画像では、REPL環境を起動した後に、「from app import db」を実行して、上に示したコードで定義されているdbオブジェクトをインポートし、それがどんなものかを表示している。「SQLALCHEMY_DATABASE_URI」キーに指定した「sqlite:///db/sample.db」が実際に参照しているファイルがどこにあるかが分かる。なお、上に示した「最小限のアプリ」は単一ファイルで構成されているので、「flask shell」コマンドを使わずとも、単に「python」コマンドでREPL環境を起動して、「from app import db」を実行した瞬間にappモジュールが読み込まれてこのFlaskアプリのコンテキストが作成されるが、アプリが複数ファイルで構成される場合には「flask shell」コマンドを使うようにしよう。
モデルの定義
次にテーブルに保存する「モデル」を定義してみよう。Flask-SQLAlchemyでは、上で作成したdbオブジェクトが持つメンバ「Model」の派生クラスとして、モデルを定義できる。よって、ここではapp.pyファイルに続けて以下のコードを記述してみよう。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db/sample.db"
db = SQLAlchemy(app)
class ToDoItem(db.Model):
__tablename__ = "todoitems"
item_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
done = db.Column(db.Boolean, nullable=False, default=False)
db.Modelは「モデルの基底クラス」であり、さまざまな機能を提供してくれる。__tablename__クラス変数にはこのモデルを保存するテーブルの名前を指定する(指定しなかった場合には、Flask-SQLAlchemyが自動的にテーブル名を決定する)。その後にある3行のクラス変数定義が「このモデルが持つカラム」の定義となる。各メンバは「db.Columnクラスのインスタンス」であり、そのコンストラクタには「カラムの型とその他のオプション」を渡す。
例えば、item_idクラス変数は「db.Integer」なので整数を保持し、プライマリキーとして機能する。カラムの型としては以下のようなものを指定可能だ(一部)。
- Integer:整数
- String(size):文字列(とその最大長)
- DateTime:日付
- Float:浮動小数点数
- Boolean:ブール値
titleクラス変数は文字列型であり、nullableがFalseなので空の値は許されない(NOT NULL制約)。doneクラス変数はブール値を保持し、nullableがFalseなので空の値は許されず、そのデフォルト値はFalseとなる。
このようにして定義したモデルを保存するデータベース(とテーブル)を作成するには、dbオブジェクトが持つcreate_allメソッドを呼び出せばよい。といっても、コードでこれを行うのではなく、REPL環境でdbオブジェクトに対してこのメソッドを呼び出すことになる。ここでは「flask shell」コマンドで起動したREPL環境を一度終了して、再度「flask shell」コマンドを実行しておく(コードの追加をアプリのコンテキストに反映するため)。create_allメソッドを呼び出しているところを以下に示す。
データベースの操作
データベースを作成したら、上で定義したモデルを実際に作成して、それをテーブルに追加できる。REPL環境を開いたままで、次のコードを実行してみよう。
from app import ToDoItem
item1 = ToDoItem(title="buy milk")
item2 = ToDoItem(title="play game!")
db.session.add(item1)
db.session.add(item2)
db.session.commit()
ここではToDoItemクラスをインポートして、そのインスタンスを2つ作成している。そして、「db.session.add」メソッドを呼び出して、これをテーブルに追加した後に、コミットを行っている。これでデータベースにデータが2つ保存された。追加したデータを読み出すには以下の2つの方法がある。
db.session.query(ToDoItem).all()
ToDoItem.query.all()
前者はデータベースセッションに対してクエリを投げている。後者はToDoItemクラスが持つヘルパー機能を使って同じことをするものだ。これらの行(と追加で幾つかの行)を実行した結果を以下に示す。
特定の条件で取得する項目をフィルタリングするには、filterメソッドやfilter_byメソッドを使う。以下は「item_idが1の項目」を取得するコードをそれぞれの方法で記述したものだ。
ToDoItem.query.filter_by(item_id=1).first()
db.session.query(ToDoItem).filter(ToDoItem.item_id==1).first()
filter_byメソッドは「キーワード引数の形式で条件を指定」する。これに対して、filterメソッドはより柔軟な形で条件を記述できる(例えば、item_idの値が100以上といった条件を記述できるのはfilterメソッドだけとなる)。
データを削除するには、db.session.deleteメソッドに削除したいデータを指定する。以下に例を示す。
db.session.add(ToDoItem(title="foo"))
db.session.commit()
item = db.session.query(ToDoItem).filter_by(title="foo").first()
db.session.delete(item)
db.session.commit()
あるいは、特定の条件に合致するデータを一括して削除するには、filter_by/filterメソッドなどで取得したデータに対してdeleteメソッドを呼び出すことも可能だ。以下に例を示す。
ToDoItem.query.filter(ToDoItem.item_id > 1).delete()
最後にデータの更新は、クエリを用いて取得したデータを変更して、それをaddメソッドで再度追加してもよいが、そのままデータベースをコミットするだけでもよい。以下に例を示す。
# addメソッドを使う例
item = ToDoItem.query.filter_by(title="buy milk").first()
item.done = True
db.session.add(item)
db.session.commit()
for item in ToDoItem.query.all():
print(item.title, item.done)
# addメソッドは使わなくてもよい
item = ToDoItem.query.filter_by(title="buy milk").first()
item.done = True
db.session.commit()
for item in ToDoItem.query.all():
print(item.title, item.done)
なお、実際のクエリがどんなものになるかはstr関数を呼び出すと確認できる(ただし、allメソッドや、ここには出ていないが先頭の項目を取得するfirstメソッド呼び出しを付加していると、結果が取り出されてしまうので注意しよう)。例えば、「str(ToDoItem.query)」は「'SELECT todoitems.item_id AS todoitems_item_id, todoitems.title AS todoitems_title, todoitems.done AS todoitems_done \nFROM todoitems'」に展開される。
Flask-SQLAlchemyを使用したデータベースのCRUD操作の基本もざっくりと見たところで、実際のToDoリストアプリのコードについて見ていくことにしよう。
Copyright© Digital Advantage Corp. All Rights Reserved.