0%

[Python] 使用 Flask-Migrate 做資料庫版本控管

前言

在 Flask application 中,我們通常會使用 Flask-SQLAlchemy (或 SQLAlchemy) 套件來操作資料庫,但它們只會在 table 不存在時才會建立新的 table, 若是之後 table 欄位有變動就需要整個 table 刪掉重建或是直接使用 SQL command 去更新 table schema,但是這兩種方式都不適合用於正式 production 的服務,而且若是直接使用 SQL command 去更新 table schema 會無法控管資料庫變更的紀錄,因此我們就需要 Flask-Migrate 這個套件來做資料庫的版控,這一篇文章就來紀錄一下使用方式。

Flask-Migrate

Flask-Migrate 是基於 alembic 再去做擴展的,能夠讓開發者更方便的整合 Flask 和 Flask-SQLAlchemy.

NOTE: 這一篇文章是以 Flask application 為主,若是使用其他框架 (Django, FastAPI) 也有相對應的 migration 方式,請參考 Django MigrationFastAPI SQL Database Migration.

Installation

1
$ pip install flask-migrate

Usage

假設目錄架構如下: (Application factories)

1
2
3
4
5
6
flask-migration-example/
├─ app/
| ├─ __init__.py
| ├─ extensions.py
| └─ models.py
└─ wsgi.py

In app/extensions.py:

1
2
3
4
5
6
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate


db = SQLAlchemy()
migrate = Migrate()

In app/models.py:

1
2
3
4
5
6
7
from .extensions import db


class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))

In app/__init__.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from .extensions import db, migrate


def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:password@host:port/database?charset=utf8'

# ...

db.init_app(app)
migrate.init_app(app, db)

# ...

return app

In wsgi.py:

1
2
3
4
5
6
7
from app import create_app

app = create_app()


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

設定好 Flask application 後,接著執行 init 指令:

1
$ flask db init

執行後會看到專案目錄底下會出現 migrations 資料夾:

db init

接著執行以下指令來產生 migration scripts:

1
2
# -m: Migration message
$ flask db migrate -m 'DB init'

最後執行 upgrade 指令將 migration script apply to database:

1
$ flask db upgrade

之後若是有更動,只要再執行 migrateupgrade 指令即可~

若是需要降回以前版本,可以使用以下指令:

1
$ flask db downgrade <revision>

其他更多指令可使用 flask db --help 查看。

在現有專案中加入 Flask-Migrate

如果是要在現有專案且資料庫已存在的情況下,想要產生 initial migration,依照上述步驟執行之後會出現以下訊息:

1
2
3
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.env] No changes in schema detected.

因為現有的資料庫對 Flask-Migrate 來說是沒有變動的,可以再依照以下步驟來產生 initial migration:

一樣先 initialize, 建立 migrations 資料夾:

1
$ flask db init

接著需要讓 Flask-Migrate 認為資料庫是空的,有兩種方式:

  • 將現有的資料庫重新命名,並再建立一個一樣名稱的空資料庫
  • 建立一個新的空資料庫,暫時讓 application 使用該資料庫

再來執行 migrate 指令:

1
$ flask db migrate

這樣就可以 initial migration, 接著就可以將資料庫回復到原本正確的狀態或使用回原本的資料庫,並把現有資料庫標記為 updated:

1
$ flask db stamp head

之後若是有更新 table schema, 一樣再使用 migrateupgrade 指令即可。

NOTE:

  • 記得要將 migrations 加入 Git 版控
  • 記得將所有環境(dev, test, production)的資料庫標記為 updated (flask db stamp head),之後就可以依照正常步驟來做 migration

Reference