# 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()`整個清空如下圖: ![](https://i.imgur.com/ahhcXBp.png) 接著執行初始化的指令,如下: ```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`目前是空資料夾,如下圖: ![](https://i.imgur.com/0Ir4w92.png) :::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產生而以,如下: ![](https://i.imgur.com/muherc3.png) ![](https://i.imgur.com/j8rTgMH.png) 觀察`versions`會發現有兩個函數,一個是`upgrade`,一個是`downgrade`,如下圖: ![](https://i.imgur.com/b5y8YXZ.png) 這與你現在所在的`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` 這時候再進資料庫看,我們的初始化資料表已經建立,如下圖: ![](https://i.imgur.com/PvYKBpP.png) ### 步驟四:降版_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` 開啟資料庫查看,資料表已刪除,如下圖: ![](https://i.imgur.com/P4hFPUF.png) ## 結論 這邊的概念跟`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`不支援欄位刪除,所以如果有欄位減少的話會異常。