Flask#DB Migrate

原文は私が書いてるBlogにのってます。よろしくおねがいします
http://soup01.com/ja/2018/07/24/flaskdb-migrate/

今回はFlask-Migrateの使え方についてメモします。まずFlask-Migrateはなんなのか公式ドキュメントをみてみると…

>Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic. The database operations are made available through the Flask command-line interface or through the Flask-Script extension.

うんうん、いわゆるFlaskのExtensionの一つでSQLAlchemyのデータベースをマイグレーションを簡単にできるってことですね〜あとこの操作はFlaskのコマンドラインかFlask-Script Extensionかのことかな。まずFlask-Migrateをインストールしましょう。

pip install Flask-Migrate

次はFile構成：

- Web
 - Manger.py

いまのFile構成はこんな感じですね、WebのFolderの下にManger.pyがあります。Manger.pyはどんなコードが入ってるかどういうど…

```py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand

app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db=SQLAlchemy(app)

migrate=Migrate(app,db)
manager=Manager(app)

manager.add_command('db',MigrateCommand)

class Person(db.Model):
	id=db.Column(db.Integer,primary_key=True)
	name=db.Column(db.String(20))
	email=db.Column(db.String(120))
	address=db.Column(db.String(120),nullable=False)
	tel=db.Column(db.String(20),nullable=False)
	password=db.Column(db.String(60),nullable=False)
	spare1=db.Column(db.String(21))

if __name__=='__main__':
	manager.run()
```

いまはFolderがDBさえ入ってはい状態でManger.pyを走ってみよう。

```shell-session
$ python Manger.py db init
Terminalでいろいろなメッセージが出てきて、終わったらFile構成がこうなります。
```

- Web
 - Manger.py
  - migrations←

migrationsというFolderが増えてきました。じゃ次はのコマンドを走りましょう。

```shell-session
$ python Manger.py db migrate

INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'person'
Generating /Users/user/Google Drive/Web/migrations/versions/c1cc8fa0d2c6_.py ... done
```

って感じですね。ほ、私が新しいTableを追加してたと気ついてたね〜そしてmigrations/versions/の中に一つのpyスクリプトが追加された。じゃこのc1cc8fa0d2c6.pyがなにが入ってるですか？

```py
"""empty message

Revision ID: c1cc8fa0d2c6
Revises:
Create Date: 2018-07-24 05:30:05.947666

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'c1cc8fa0d2c6'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
 op.create_table('person',
 sa.Column('id', sa.Integer(), nullable=False),
 sa.Column('name', sa.String(length=20), nullable=True),
 sa.Column('email', sa.String(length=120), nullable=True),
 sa.Column('address', sa.String(length=120), nullable=False),
 sa.Column('tel', sa.String(length=20), nullable=False),
 sa.Column('password', sa.String(length=60), nullable=False),
 sa.Column('spare1', sa.String(length=21), nullable=True),
 sa.PrimaryKeyConstraint('id')
)


def downgrade():
 op.drop_table('person')
```

Manger.pyの中に定義されたClassがここでDBに入れるってわけですね。じゃ最後のコマンドを走りましょう。

```shell-session
$ python Manger.py db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> c1cc8fa0d2c6, empty message
```

- Web
 - Manger.py
 - migrations
 - test.db←

おお、DBが作成されました！じゃ、最後はsqlite3コマンドで確認しましょうか。

```sql
sqlite3 test.db

sqlite>.schema

CREATE TABLE person (
	id INTEGER NOT NULL, 
	name VARCHAR(20), 
	email VARCHAR(120), 
	address VARCHAR(120) NOT NULL, 
	tel VARCHAR(20) NOT NULL, 
	password VARCHAR(60) NOT NULL, 
	spare1 VARCHAR(21), 
	PRIMARY KEY (id)
);
```

OK、それで大丈夫だね。次はManger.pyにもうひとつのTableを追加しましょう。

```py
class tempClass(db.Model):
	id=db.Column(db.Integer,primary_key=True)
	title=db.Column(db.String(20))
```

そしてもう一度コマンドを走ります。

```shell-session
$ python Manger.py db migrate

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'temp_clasee'
  Generating /Users/user/Google Drive/Web/migrations/versions/6c77edaef8c4_.py ... done

$ python Manger.py db upgrade

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade c1cc8fa0d2c6 -> 6c77edaef8c4, empty message
```

どうやらうまくいけそうですね。

```sql
sqlite3 test.db
sqlite>.schema

CREATE TABLE person (
	id INTEGER NOT NULL, 
	name VARCHAR(20), 
	email VARCHAR(120), 
	address VARCHAR(120) NOT NULL, 
	tel VARCHAR(20) NOT NULL, 
	password VARCHAR(60) NOT NULL, 
	spare1 VARCHAR(21), 
	PRIMARY KEY (id)
);
CREATE TABLE temp_clasee (
	id INTEGER NOT NULL, 
	title VARCHAR(20), 
	PRIMARY KEY (id)
);
```

うん、それは大丈夫だね！

実はいくつの問題がありますが…

- Tableの内容が修正されてMigrateしてもなにも変わらない。
 - ネットでみたらどうやら100％修正したものをDetectできるわけでもないらしいのでmigrateのコマンド走ったあとは必ずそのとき作成されたpyを一回みること。
- nullable=Falseのコラムがupgradeコマンド走るときはエラー出る問題
 - うん…これはわかりません。いまはとりあえずnullableを入れないで、DBがマイグレーション終わったら手動でSqliteコマンドでCOPYｰ>修正ｰ>貼り付けって感じですね。参考Link：[sqlite3 - Modify Sqlite Table Column NOT NULL to NULL - Stack Overflow](https://stackoverflow.com/questions/29583396/modify-sqlite-table-column-not-null-to-null)

それじゃねー