Flaskでデータベースをアクセスしてみよう
データベース
Flaskでデータベースを利用するための拡張モジュールには次のものがあります。
SQLalchemy:データベースにアクセスをできるようにする
Flask-SQLAlchemy:SQLAlchemy をFlaskで利用するために必要
Alembic:データベースのマイグレーションが楽にできる
Flask-Migrate:AlembicをFlaskで利用するために必要
Flask-SQLAlchemy はオブジェクトリレーショナルマッパー(ORM) として良く利用されているSQLAlchemyをFlaskから使えるようにするための拡張機能です。 ORMが内部で行っていることは、高レベルの操作をデータベースコマンド(SQL)に変換することで、ORMを使用すると、アプリケーションは、テーブルやSQLではなく、クラス、オブジェクト、メソッドなどでデータベースにアクセスすることができます。
SQLAlchemyが好まれる理由には、MySQL、PostgreSQL、SQLiteなど、多数のデータベースエンジンをサポートしていることがあげられます。つまり、開発段階では、サーバーを必要としないシンプルなSQLiteデータベースを使用し、アプリケーションを本番サーバーにデプロイするときは、より堅牢なMySQLまたはPostgreSQLサーバーを利用することができます。
データベースのマイグレーション
アプリケーションを開発/保守を行っているとき、データベースの変更や拡大が必要になり、既存のデータベースを更新しなければいけないという、手間のかかる問題が存在します。
多くのリレーショナルデータベースでは、構造化データを中心としているため、構造が変更されたときには、既に存在するデータを変更された構造に移行する必要があります。
SQLAlchemy のマイグレーションのためのフレームワークがAlembicで、Flask-Migrateは、AlembicをFlaskから利用できるようにするための機能拡張です。
Alembicを利用すると、データベースの利用開始には少し作業が増えますが、将来データベースに変更を加えるためときは、大幅に作業工数を短縮することができます。
開発準備
ディレクトリ作成
Flask にはプロジェクトという概念がありません。
アプリケーションごとにディレクトリを作成しておくようにしましょう。
code: bash
$ mkdir dbdemo
$ cd dbdemo
$ mkdir app templates
サポートモジュールをインストール
インストールは pip で行います。
code: bash
$ pip install flask-sqlalchemy flask-migrate
設定ファイル
アプリケーションの開発中は、Pythonの標準モジュールとして利用できる、SQLiteデータベースを使用するようにします。
設定ファイルconfig.pyを作成しましょう。
code: app/config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'K24rMLtDxlbW_PLoQQrAwg'
#
DATABASE_NAME = os.environ.get('DATABASE_NAME') or 'app.db'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, DATABASE_NAME)
SQLALCHEMY_TRACK_MODIFICATIONS = False
Flask-SQLAlchemy は、変数SQLALCHEMY_DATABASE_URI からデータベースの接続先を取得します。
Flask-SQLAlchemy はデータベースで変更が行われるたびにアプリケーションに通知します。
変数SQLALCHEMY_TRACK_MODIFICATIONSにFalseがセットされていると、この機能は抑制されます。
Flaskの初期化処理を作成しておきましょう。
code: app/__init__.py
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
from app import routes, models
初期化処理で、Flaskのアプリケーションを表すオブジェクトapp、
データベースを表すオブジェクトをdb、データベースマイグレーションエンジンを表すオブジェクト migrateを保存しています。
このままでは、最後の行でエラーになるので次のようにファイルを作成しておきます。
code: bash
$ touch app/routes.py
$ touch app/models.py
app/routes.py に URLにマップするビュー関数、app/models.py にデーターを処理するモデル関数を記述するようにします。
データベーススキーマの作成
データベーススキーマ(Database Schemas) は、データベースの構造を表すことばで、テーブルなどもスキーマの一部になり、スキーマが異なれば、同じ名前のテーブルを作ることができます。簡単に説明するとデータベースの構造と理解できるものです。
データベースは一度作成して、データが蓄積すればするほどスキームを変更することが簡単ではなくなるので、はじめにきちんと検討することが大切です。
ユーザ情報を格納するデータベース users を次のように作成しましょう。
table: データベーススキーム users
フィールド名 型 説明
id INTERGER ユーザID
username VARCHAR(16) ユーザ名
email VARCHAR(120) Email アドレス
password VARCHAR(128) パスワード
これを定義するクラスUserを定義します。
code: app/modules.py
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(16), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password = db.Column(db.String(32))
def __repr__(self):
return f'<User {self.username}>'
ここで定義しているUserクラスは、Flask-SQLAlchemyからクラスdb.Modelを継承しています。このクラスでusernameなどのフィールドをクラス変数として定義します。 フィールドは、db.Columnクラスのインスタンスオブジェクトとして保存されます。
このオブジェクト、フィールドタイプをやその他のオプションを引数として与えています。
primary_key=True は、このフィールドがデータベースのなかで重複しないデータだと定義しています。 プライマリキーを定義することは、データベース検索を効率的にするためには、とても重要なことです 。
__repr__() の特殊メソッドは、デバッグやロギングのときに役立ちます。
code: bash
$ flask shell
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 13:42:17)
IPython: 7.13.0
Instance: /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/dbdemo/instance
In 1: from app.models import User In 2: user = User(username='iisaka51', email='iisaka51@example.com') In 3: user Out3: <User iisaka51> faskコマンドは、環境変数FLASK_APPを参照して、Flaskアプリケーションの場所を把握していることに留意してください。
マイグレーション・リポジトリ
modulesモジュールで定義したクラスUserは、このアプリケーションで使用されるデータベーススキーマになります。
アプリケーションの開発がすすみ機能を増えるにつれて、データベーススキーマは変更する必要が出てきます。多くの場合は、フィールドを追加する可能性が高いのですが、アイテムを変更または削除するようなこともあります。 Alembicは、データベースを最初から再作成する必要がない方法でこれらのスキーマを変更することを可能にします。
Alembicは、マイグレーション・リポジトリを管理してくれます。ここには、データベース更新のためのスクリプトを格納されます。データベーススキーマが変更されるたびに、変更の詳細を含むマイグレーション・スクリプトがリポジトリに追加されます。マイグレーションをデータベースに適用するときは、これらのスクリプトが作成された順序で実行されます。
Flask-Migrateは、flaskコマンドのサブコマンドdbを提供していて、データベースのマイグレーションの管理ができるようになります。
code: bash
$ flask db --help
Perform database migrations.
Options:
--help Show this message and exit.
Commands:
branches Show current branch points
current Display the current revision for each database.
downgrade Revert to a previous version
edit Edit a revision file
heads Show current available heads in the script directory
history List changeset scripts in chronological order.
init Creates a new migration repository.
merge Merge two revisions together, creating a new revision file
migrate Autogenerate a new revision file (Alias for 'revision...
revision Create a new revision file.
show Show the revision denoted by the given symbol.
stamp 'stamp' the revision table with the given revision; don't run...
upgrade Upgrade to a later version
マイグレーション・リポジトリを作成しましょう。
code: bash
$ flask db init
Creating directory /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/
flask/dbdemo/migrations ... done
Creating directory /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/
flask/dbdemo/migrations/versions ... done
Generating /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/db
demo/migrations/script.py.mako ... done
Generating /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/db
demo/migrations/env.py ... done
Generating /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/db
demo/migrations/README ... done
Generating /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/db
demo/migrations/alembic.ini ... done
Please edit configuration/connection/logging settings in '/Users/goichiiisaka/Down
loads/Python.Osaka/WebApplicationClass/flask/dbdemo/migrations/alembic.ini' before
proceeding.
$ ls -lCF migrations/
README alembic.ini env.py script.py.mako versions/
ディレクトリ migrations 以下のファイルは、今後このプロジェクトの一部として扱う必要があるので、Gitなどのバージョン管理システムなどを利用するのであれば、アプリケーションと一緒に追加しておきましょう。
データベース・マイグレーション
マイグレーション・リポジトリができたので、最初にデータベース・マイグレーションを実行しましょう。
Alembicはデータベースモデル(app/models.py)で定義されているデータベーススキーマを、現在データベースで使用されている既存のデータベーススキーマと比較します。
次に、データベーススキーマをアプリケーションモデルに一致させるために必要な変更を、マイグレーション・スクリプトに入力します。
この例では、まだデータベースがないため、ユーザーモデル(クラスUser)がマイグレーション・スクリプトに追加されます。
code: bash
$ flask db migrate --help
Autogenerate a new revision file (Alias for 'revision --autogenerate')
Options:
-d, --directory TEXT Migration script directory (default is "migrations")
-m, --message TEXT Revision message
--sql Don't emit SQL to database - dump to standard output
instead
--head TEXT Specify head revision or <branchname>@head to base new
revision on
--splice Allow a non-head revision as the "head" to splice onto
--branch-label TEXT Specify a branch label to apply to the new revision
--version-path TEXT Specify specific path from config for version file
--rev-id TEXT Specify a hardcoded revision id instead of generating
one
-x, --x-arg TEXT Additional arguments consumed by custom env.py scripts
--help Show this message and exit.
code: bash
$ flask db migrate -m "users table"
Generating /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/db
demo/migrations/versions/87cdb7ace9b5_users_table.py ... done
app/config.py で SQLALCHEMY_DATABASE_URI はデフォルトでは、SQLiteを使用するようにしている(sqlite:///...)ので、app.dbというファイルが作成されます。
Flask-SQLAlchemyは、デフォルトでデータベースのテーブルにスネークケース(snake case)の命名規則(補足説明1を参照)を使用します。今回のUserモデルの場合、データベース内の対応するテーブルはuserという名前になります。
テーブル名を指定したい場合は、__ tablename__というアトリビュートに設定して、モデルクラスに追加します。
スネークケース命名規則 (Snake Case Naming Rule)
ハイフォン(-)もしくはアンダースコア(_)で区切られて続く命名法
PythonOsaka の場合は、python_osaka となります。
データベースモデルを反映
アップグレード
開発マシンにアプリケーションがあり、オンラインで使用中の本番サーバーにコピーがデプロイされていると想定します。
次のアプリケーションのリリースで、モデルに変更を加える必要がある場合を考えてみましょう。マイグレーション・フレームワークがなければ、開発マシンとサーバーの両方でデータベースのスキーマを変更する方法を開発者(あるいは運用管理者)が理解する必要があります。
Flask-migrate を利用すると、アプリケーションのモデルを変更したときは、新しいマイグレーション・スクリプトを作成して(flask db migrate)、自動生成が正しいことを確認してから、開発マシンのデータベースに変更を適用します(flask db upgrade)。
code: bash
$ flask db upgrade
アプリケーションの新しいバージョンを運用サーバーにリリースする準備ができたら、新しいマイグレーション・スクリプトを含むアプリケーションの更新されたバージョンを取得して、flask db upgradeを実行するだけで済みます。
ダウングレード
直近のマイグレーションを元に戻す flask db downgradeコマンドもあります。本番システムではこのコマンドが必要になることはほとんどありませんが、アプリケーションの開発や保守の工程では、とても有用になることがあります。
Flaskでデータベースにエントリを追加
SQLAlchemy オブジェクト db には データベースと接続した sessionオブジェクトを持っていて、add() と commit() メソッドでエントリを追加することができます。
code: IPython
In 2: # %load add_user.py ...: from app import db
...: from app.models import User
...:
...: user = User(username='python', email='python@example.com')
...: db.session.add(user)
...:
In 3: user = User(username='freddie', email='freddie@example.com') In 4: db.session.add(user) In 5: db.session.commit() Flaskでデータベースから読み出す
Userモデル(クラス)はdb.Models を継承しているので、SQLクエリを発行するUser.queryオブジェクトを持っています。このオブジェクトの all() を使用すると、全てのエントリを取得します。
code: Ipython
In 10: for user in users: ...: print(f'ID:{user.id} Name:{user.username}')
...:
ID:1 Name:python
ID:2 Name:freddie
Flaskでデータベースのエントリを削除
db.session.delete() でデータベースからエントリを削除することができます。
code: IPython
In 11: users = User.query.all() In 12: for user in users: ...: db.session.delete(user)
...:
In 13: users = User.query.all() In 14: users Out14: [] In 15: db.session.commit() flask shell
これまで説明してきませんでいたが、Flask には対話型に実行できるflask shellコマンドがあります。
python との違い、Flask の初期化を行ってくれます。
また、拡張モジュール flask-shell-ipython がインストールされていると、python の代わりに、IPython が立ち上がってきます。
code: bash
$ python
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 13:42:17)
Type "help", "copyright", "credits" or "license" for more information.
>> app
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'app' is not defined
>>
code: bash
$ flask shell
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 13:42:17)
IPython: 7.13.0
Instance: /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/dbdemo/instance
In 1: app Out1: <Flask 'app'> それでも、毎回モジュールをインポートするのが面倒なので次のようにしておきます。
code: myapp.py
from app import app, db
from app.models import User
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User}
これを、python-dotenv に登録しておきます。
code: bash
$ echo FLASK_APP=myapp.py > .flaskenv
これで、db と User は flask shell を起動してすぐに使えるようになります。
code: Ipython
$ flask shell
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 13:42:17)
IPython: 7.13.0
Instance: /Users/goichiiisaka/Downloads/Python.Osaka/WebApplicationClass/flask/dbdemo/instance
In 1: User Out1: app.models.User 参考: