# Flask實作_ext_06_Flask-Migrate
###### tags: `flask` `flask_ext` `python`
:::danger
官方文件:
* [Flask-Migrate](https://flask-migrate.readthedocs.io/en/latest/)
前置閱讀:
* [Flask實作_ext_05_Flask-Script_命令列調式專案](https://hackmd.io/s/Hy9Xfb6mM)
:::
## 說明
`sqlalchemy`只有在`table`不存在的時候才會建置產生一個全新的`table`,但對於任何的欄位編修異動都必需整張表刪除重建,在開發中還可以,上了線的系統實在很難以這種方式下去處理。
`sqlalchemy`團隊注意到這個問題,所以開發了`alembic`,而Flask社群也注意到,所以有了`flask-migrate`。
基本上`flask-migrate`的底層還是`alembic`,只是包裝讓開發人員使用方便,配合`flask-script`在資料庫的搬移中省了不少工作<sub>(目前已不建議使用`flask-script`,後續再調整至`CLI`)</sub>。
## 安裝
```python=
pip install flask-migrate
```
## 指令說明
`flask-migrate`的常用指令就四個,精確來說最常使用到的指令會是後面三個,因為初始化只會執行一次。
```shell=
$ python manage.py db init # 初始化資料庫
$ python manage.py db migrate # 建置腳本
$ python manage.py db upgrade # 更新
$ python manage.py db downgrade # 降版
```
## 範例
### 步驟一:初始化資料庫\_init
`flask_migrate`包含兩個類別分別為`Migrate`與`MigrateCommand`,`Migrate`包了所有的功能了,而`MigrateCommand`是配合`flask-script`使用。
`flask_migrate`也可以使用`init_app`來初始化,這部份在配合工廠函數配置的時候很實用。
下面的範例是利用`flask-script`來實作,因此導入了`MigrateCommand`,並且當做`add_command`的參數,大致配置如下:
```python=
from app_blog import app
from app_blog import db
from flask_script import Manager, Command, Shell
from flask_migrate import Migrate, MigrateCommand
manager = Manager(app)
migrate = Migrate(app, db) # 註1
manager.add_command('db', MigrateCommand)
if __name__ == "__main__":
manager.run()
# 註1:把migrate = Migrate(app, db)寫在app_blog\\_\_init\_\_.py也可以。
```
第7行:實作`migarte`的時候要放兩個參數,一個是實作Flask的`app`,另一個是實作`sqlalchemy`的`db`,這兩個物件的命名皆依各自專案設置。
第9行:利用`flask-script`的`add_command`來加入指令,命名為`db`,請不要與資料庫的`db`混為一談,只是範例命名剛好相同而以。
配置好之後,我們先看一下資料庫,執行之前我先將資料庫`drop_all()`整個清空如下圖:

接著執行初始化的指令,如下:
```shell=
(venv) D:\proPycharm\app_blog>python manager.py db init
Creating directory D:\proPycharm\app_blog\migrations ... done
Creating directory D:\proPycharm\app_blog\migrations\versions ... done
Generating D:\proPycharm\app_blog\migrations\alembic.ini ... done
Generating D:\proPycharm\app_blog\migrations\env.py ... done
Generating D:\proPycharm\app_blog\migrations\README ... done
Generating D:\proPycharm\app_blog\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'D:\\proPycharm\\app_blog\\migrations\\alembic.ini' before proceeding.
```
第1行:執行命令,這邊的`db`為我們設置的命令,而該命令與`MigrateCommand`綁定,執行之後專案內會產生一個資料夾`migrations`
執行命令之後產生的資料夾我們可以發現`migrations\versions`目前是空資料夾,如下圖:

:::info
`flask-migrate`可以一次處理多個資料庫,這部份可以配合`flask-sqlalchemy`使用,命令如下:
```shell
python db init --multidb
```
:::
### 步驟二:建置腳本_migrate
```shell=
(venv) D:\proPycharm\app_blog>python manager.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 'UserRgeisters'
INFO [alembic.autogenerate.compare] Detected added table 'users'
INFO [alembic.autogenerate.compare] Detected added table 'contacts'
Generating D:\proPycharm\app_blog\migrations\versions\9cc5477351c8_.py ... done
```
第1行:建置腳本
這時候會發現`versions`裡面多了一個Python文件,這是版本資訊,裡面有兩段,一段是你執行`upgrade`的話會建立table,一個是你執行`downgrade`的話會刪掉table。
目前為止資料庫還是空的,只有記錄版本的table產生而以,如下:


觀察`versions`會發現有兩個函數,一個是`upgrade`,一個是`downgrade`,如下圖:

這與你現在所在的`heads`有關,你所在的`heads`為何,執行之後就是如何,透過下面指令可以查詢目前的`heads`
```shell=
(venv) D:\proPycharm\app_blog>python manager.py db heads
9cc5477351c8 (head)
```
### 步驟三:更新_upgrade
```shell=
(venv) D:\proPycharm\app_blog>python manager.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 -> 9cc5477351c8, empty message
```
第1行:執行腳本`upgrade`
這時候再進資料庫看,我們的初始化資料表已經建立,如下圖:

### 步驟四:降版_downgrade
```shell=
(venv) D:\proPycharm\app_blog>python manager.py db downgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running downgrade 9cc5477351c8 -> , empty message
```
第1行:執行降版`downgrade`
開啟資料庫查看,資料表已刪除,如下圖:

## 結論
這邊的概念跟`git`其實很像,因為套件還提供分支、合併的概念,所以你必需確認你目前的`headS`去對應`script`<sub>(`versions`內Python文件)</sub>來執行相對應的`upgrade`與`downgrade`才不會造成錯誤。
記得,只有第一次執行的時候需要`init`,後續就是一直循環步驟二、三、四<sub>(看有沒有需求降版)</sub>
## 其它
### 相關指令
基本上指令看了一下會發現,跟git好像有點像,應該是有帶入相關的概念!當然,因為是將alebmic包裝起來使用,不少文件資料還是需要查一下alebmic。
* flask db --help
可以查詢相關指令,執行之後會跑出很多實用指令。
* flask db init [--multidb]
初始化資料庫
* flask db revision
.....
* flask db migrate
建置腳本,會自動對異動的db做腳本的建立,這時候不會影響到資料庫
* flask db edit
.......
* flask db upgrade
如果沒有指定版本,則以最新版本來更新
* flask db downgrade
資料庫降版,如果沒有指令就以上版還原
* flask db stamp
....
* flask db current
顯示當前版本
* flask db history
顯示歷史歷程
* flask db show
顯示當前版本詳細資訊
* flask db merge
合併兩個版本
* flask db heads
顯示目前版本號
* flask db branches
顯示當前分支點
### 注意
* `sqlite`不支援欄位刪除,所以如果有欄位減少的話會異常。