Flask_Migrateは、SQLAlchemyベースのDBをマイグレーションする為のFlask Extensionです。
Alembic is a database migration tool for SQLAlchemy.
https://flask-migrate.readthedocs.io/en/latest/
◇ Step 1 :
pip install flask-migrate
◇ Step 2 : modify main.py
import os
from flask import Flask
from flask_migrate import Migrate // Imported Flask Migrate
from api.db import db
from api.config import config
......
db.init_app(app)
migrate = Migrate(app, db) // It will allow us to run migrations
......
@app.before_first_request
def create_tables():
db.create_all()
if __name__ == '__main__':
app.run()
==以下はコマンドライン操作
◇ Step 3 :Creation of Migration Repository (Directory).
flask dbの変更記録を保存するレポジトリーを作成する。
> export FLASK_APP=run.py (Windowsは set FLASK_APP=run.py)
> flask db init (<- これでレポジトリ用のフォルダが作成される)
Flask-Migrate exposes its commands through the flask command. You have already seen flask run, which
is a sub-command that is native to Flask. The flask db sub-command is added by Flask-Migrate to manage
everything related to database migrations. So let's create the migration repository for microblog by
running flask db init:
◇ Step 4 :(generate an initial migration:)
flask db migrate -m "コメント"
-m ”コメント”は、file名に付加される任意情報。なくても良い。
(Alembicで認識できるDBの変更には制限がある。)
Alembic is currently unable to detect table name changes, column name changes, or anonymously named constraints.
既にあるDBにFlask_Migrateを適用して後から管理しようとすると、この段階で
No changes in schema detected. 等のMessageがでる。
対応方法:How To Add Flask-Migrate To An Existing Project (2020/04/10)
https://youtu.be/IxCBjUapkWk
◇ Step 5 :
flask db upgrade
or
flask db downgrade
Because this application uses SQLite, the upgrade command will detect that a database does not exist
and will create it (you will notice a file named app.db is added after this command finishes, that is
the SQLite database). When working with database servers such as MySQL and PostgreSQL, you have to create the database in the database server before running upgrade.
You also have a flask db downgrade command, which undoes the last migration.
Flask dbで使えるその他のオプション: flask db とタイプしてリターンすると表示される。
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
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
ということでやってみる。
tw_msg.py
import os
from flask import Flask, render_template,url_for,flash, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length
from flask_migrate import Migrate
app = Flask(__name__)
project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "msg.db"))
app.config["SQLALCHEMY_DATABASE_URI"] = database_file
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] ='you-will-never-know'
db = SQLAlchemy(app)
db.init_app(app)
migrate = Migrate(app, db)
#DB Model
class Tw_msg (db.Model):
__tablename__ = 'tw_msg'
id = db.Column(db.Integer, unique=True, primary_key=True)
time = db.Column(db.String(6))
message = db.Column(db.String(140))
hashtag =db.Column(db.String(140))
url = db.Column(db.String(140))
#WTF Model(入力フィールド定義)
class Message_Add(FlaskForm):
id = StringField('ID',validators=[DataRequired(), Length(min=1, max=3) ])
time = StringField('Time',
validators=[DataRequired(), Length(min=2, max=60) ])
message = StringField('Message')
hashtag = StringField('Hashtag')
url = StringField('URL')
submit = SubmitField('Submit')
#ルーティングと処理
@app.route('/',methods=['GET', 'POST'])
def message():
# db.create_all()
#db.create_all()は、windows環境ではあっても動作するが(Migrateが認識しない空白のオペレー
ションが出来てしまうことになるが)、CentOS環境では動作しない。(DBは自動生成されない。)
従って削除して、wtfで作成した方が良い。
#テーブルを新しく追加する時も、既に既存のtableがある場合、エラーになる。
form = Message_Add()
#新規データ受付時処理
try:
if form.validate_on_submit():
message = Tw_msg( time = form.time.data, message = form.message.data ,
hashtag = form.hashtag.data, url = form.url.data )
db.session.add(message)
db.session.commit()
flash('New message has been created!', 'success')
return redirect(url_for('message'))
except:
flash('Failed at somewhere!', 'Unsuccess')
#登録データ一覧表示
messages = Tw_msg.query.all()
return render_template("message.html",form=form, messages=messages)
@app.route("/<int:message_id>/edit", methods=['GET', "POST"])
def edit_message(message_id):
msg = Tw_msg.query.get(message_id)
form = Message_Add()
if form.validate_on_submit():
msg.id = form.id.data
msg.time = form.time.data
msg.message = form.message.data
msg.hashtag = form.hashtag.data
msg.url = form.url.data
db.session.commit()
flash('Your post has been updated!', 'success')
return redirect(url_for('message'))
elif request.method == 'GET':
form.id.data = msg.id
form.time.data = msg.time
form.message.data = msg.message
form.hashtag.data = msg.hashtag
form.url.data = msg.url
#return "I'm doin' fine."
return render_template("edit_message.html",form=form)
@app.route("/<int:message_id>/delete", methods=['GET', "POST"])
def delete_message(message_id):
msg = Tw_msg.query.get_or_404(message_id)
db.session.delete(msg)
db.session.commit()
flash('Your post has been deleted!', 'success')
return redirect(url_for('message'))
==さて、実行してみる。
C:\Users\[ユーザ名]\Desktop\top>set FLASK_APP=tw_msg.py
C:\Users\[ユーザ名]\Desktop\top>flask db init
Creating directory C:\Users\[ユーザ名]\Desktop\top\migrations ... done
Creating directory C:\Users\[ユーザ名]\Desktop\top\migrations\versions ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\alembic.ini ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\env.py ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\README ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\Users\\[ユーザ名]\\Desktop\\top\\migrations\\alembic.ini' bef
ore proceeding.
で、Current Directoryにmigrationsというフォルダができて、以下のようなファイルが格納されている。
次に、現状を確認
>>> from tw_msg import db
>>> from tw_msg import Tw_msg>>> a = Tw_msg.query.all()
>>> a[0].__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000000004E9D940>, 'hashtag': '# ハッシュタグ', 'time': '12:20', 'url': 'https://mossymob.net', 'message': 'メッセージ1', 'id': 1}
こんな感じ。
#DB Model
class Tw_msg (db.Model):__tablename__ = 'tw_msg'
id = db.Column(db.Integer, unique=True, primary_key=True)
time = db.Column(db.String(6))
message = db.Column(db.String(140))
hashtag =db.Column(db.String(140))
url = db.Column(db.String(140))
length = db.Column(db.String(10))
と、1つコラムを追加。
>flask db migrate "add length column" "コメントをつけるとエラー。なしで実行。
(venv) C:\Users\Toshiro\Desktop\top>flask db migrate
INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'tw_msg.length'
Generating C:\Users\Toshiro\Desktop\top\migrations\versions\9da06fe9a5aa_.py ... done
migrationsフォルダの中に\versionsフォルダができて
9da06fe9a5aa_.pyが作成された。
中身は、
"""empty message
Revision ID: 9da06fe9a5aa
Revises: Create Date: 2019-08-08 01:43:33.173202
"""
from alembic import opimport sqlalchemy as sa
revision = '9da06fe9a5aa'
down_revision = None
branch_labels = None
depends_on = None
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tw_msg', sa.Column('length', sa.String(length=10), nullable=True))
# ### end Alembic commands ###
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('tw_msg', 'length')
# ### end Alembic commands ###
良さそうなので、
(venv) C:\Users\ユーザ名\Desktop\top> flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 9da06fe9a5aa, empty message
>>> from tw_msg import db
>>> from tw_msg import Tw_msg>>> a= Tw_msg.query.all()
>>> a[0].__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000000004ABAA58>, 'length': None, 'hashtag': '#
ハッシュタグ', 'time': '12:20', 'url': 'https://mossymob.net', 'message': 'メッセージ1', 'id': 1}
ということで、既存のデータを変更せずに、コラムの追加に成功した。
(( トラブル対応))
migrationsフォルダーをdeleteしても、upgrade/downgradeが出来ない時がある。
alembic_version という名前のテーブルが残ってしまって、バージョン管理に齟齬が出てしまっているから。 layerが違うらしうく、通常のテーブル検索では表示されないが、普通のテーブルの消去の仕方でテーブルを消せる。
dropTableStatement = "DROP TABLE alembic_version"
cursor.execute(dropTableStatement)
等、
baseに戻ってみるのもあるかも。
from flask_migrate import downgrade
downgrade(revision='base')
自分の事象は、これでは上手くいかなかったけど。
(追記)
あれ、alembic tableを取得できる場合もあるね。 混乱中。
>>> cursor.execute("select * from sqlite_master where type='table'")
<sqlite3.Cursor object at 0x7f6a1f5e01f0>
>>> for x in cursor.fetchall():
... print(x)
...
('table', 'alembic_version', 'alembic_version', 2, 'CREATE TABLE alembic_version (\n\tversion_num VARCHAR(32) NOT NULL, \n\tCONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)\n)')
0 件のコメント:
コメントを投稿