はじめに
Flaskでデータベースを扱う場合、OR MapperとしてSQLAlchemyがまだ今の所デファクトスタンダードだと思われる。
個人的にはあまりライブラリ依存したくないのでFlask-SQLAlchemyを使わずSQLAlchemyをそのまま使うようにしている(WTformsとかもね)
ただそういうこだわりをもっていて何か大切なものを見失っては困るので大分前だがとあるプロジェクトにFlask-SQLAlchemyを導入した。
そうなるとFlask-Migrateにもお世話になるのが筋だと思いこちらも使ってみた。
Flask-Migrateについてはよく理解していないとハマる。
実はつい今しがたもそのプロジェクトのテスト環境を久々に作ろうとしてツボったのだ。
そのため備忘録というか少しでも参考になればと思い、私なりの使い方を紹介する。
基本的な使い方
一番最初はこの三点セット
flask db init # マイグレーションのリポジトリ初期化(git initみたいなもん)
flask db migrate # マイグレーション用のスクリプトを生成(migrations/versions/配下のxxx_.py)
flask db upgrade # データベースへ書き込み
実際は FLASK_APP=run:app flask db init みたいに環境変数が必要
後はモデルの修正するたびに
flask db migrate
flask db upgrade
ってやればよい。リビジョン管理がされているのでロールバックというかダウングレードもできる。
flask db downgrade
以上、終了。
でだいたいどこに書いてある記事でもこんな説明になっている。
悩ましいのは生成されるmigrationsをgitの管理下に置くかということだ。
migrationsディレクトリって環境毎につくるもの?
恐らくこれが正解だと思う。
Flaskアプリの環境とDBが分離していればよほど問題にならない。
私の場合Dockerのコンテナに両方を入れているので新しい環境にデプロイした場合データベースの同期をしない限り新しいデータベースが作成される。
そうすると新規でデータベースを作る時に次のような問題が生じる。
モデルの変更が一度もない場合だとflask db migrate && flask db upgrade
ですんなりテーブル作成できる。
そうでない場合、flask db migrate
は更新の差分情報しかもっていないのでflask db upgrade
でこける。
こんなふうにしてみた
新しい環境にアプリを移行してデータベースを初期化する場合は以下のような回りくどい手順を実行すればなんとかなる。
現在のリビジョンを調べる
元となるmigrationsのリビジョンを調べる。
またはmigrations/versions/配下のファイルを漁るかmysqlなら
mysql> select * from alembic_version
+--------------+
| version_num |
+--------------+
| abcdefghijkl |
+--------------+
最新リビジョンはabcdefghijkl
だとわかる。
flask db history というコマンドもあるようだが使ったこと無いので未確認
新規migrationsを作成する
次に新しい環境にデプロイされたmigrationsディレクトリを一旦退避する。
mv migrations migrations.bak
新しい環境で初期化の三点セットを実行
flask db init
flask db migrate
flask db upgrade
ここで発行されるリビジョンがxxxxxxxxxx
だとする。
元のリビジョン番号に戻す
migrationsを元に戻す。
mv migrations migrations.new
mv migrations.bak migrations
最後にテーブルを書き換える
update into alembic_version set version_num='abcdefghijkl' where version_num='xxxxxxxxxxxx'
以上。
とここまで書いたが。。。
自分の書いたREADMEを読み直してみると
新しい環境でデータベースを初期化したい場合はきっとこちらが正しい
最初にユーザとデータベースの作成
GRANT ALL PRIVILEGES ON *.* TO foo@"%" IDENTIFIED BY 'foo' WITH GRANT OPTION;
CREATE DATABASE mysite;
モデルからテーブルを作成
FLASK_APP=run:app flask shell
>>> from app.database import db
>>> db.create_all()
>>> db.session.commit()
マイグレーション実行
FLASK_APP=run:app flask db migrate
FLASK_APP=run:app flask db stamp head
FLASK_APP=run:app flask db upgrade
参考
run.py
import os
from app import create_app
config = os.getenv('FLASK_ENV', 'production')
app = create_app(config)
if __name__=='__main__':
app.run(host='0.0.0.0', debug=True)
config.py
(抜粋)
# 省略
class Config(object):
## Default constants
DEBUG = False
TESTING = False
DEVELOPMENT = False
# 省略
class ProductionConfig(Config):
pass
class StagingConfig(Config):
DEVELOPMENT = True
class DevelopmentConfig(Config):
DEBUG = True
DEVELOPMENT = True
class TestingConfig(Config):
DEBUG = True
TESTING = True
app_config = {
'production': ProductionConfig,
'staging': StagingConfig,
'development': DevelopmentConfig,
'testing': TestingConfig,
}
app/__init__.py
(抜粋)
# 省略
from flask import Flask
from config import app_config
def create_app(config, local_config='config.py'):
# create flask instance
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(app_config[config])
# 省略
return app
app/database.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
def init_db(app, migrate_dir=None):
db.init_app(app)
Migrate(app, db, migrate_dir) if migrate_dir else Migrate(app, db)
とある。うっ汗。
いやまあ手動でリビジョン直したい場合がどこかであるかもってことで。。。(いや、実際にどこかであったぞ)