Python Web Flask — 實作 Flask-Migrate更新資料庫

Sean Yeh
Python Everywhere -from Beginner to Advanced
13 min readDec 16, 2020

--

Mont-Tremblant, Canada, photo by Sean Yeh

先前我們透過Flask-SQLAlchemy實作資料庫建立了一個三個欄位的資料表table。

今天我們要再來深入一點探討,當資料庫的資料表有變更時該如何處理?

回顧

在進一部探討前,我們先回顧一下關於資料庫建置,我們已知的部分。

建立資料庫的步驟

我們已經知道在Flask裡面建立SQLLite資料庫,需要下面五個步驟:

  1. 匯入相關套件
  2. 建立資料庫路徑
  3. 建立Flask App
  4. Configure 參數設定
  5. 傳到SQLAlchemy

透過這種方式建立料庫,我們就不需要自行手動建立SQL資料表。這裡的Class Model可以直接連結到資料庫的table資料表。

建立Model的步驟

另外,建立資料庫的Model與建立表單一樣,這裏需要下面五個步驟:

  1. 建立Class類別
  2. 類別繼承自db.Model
  3. 提供資料表的名稱(選用)
  4. 建立資料表欄位
  5. 加上__init__與__repr__

實作 1:建立資料庫

回顧完畢後,我們可以依照上面的步驟實作一下。建立一個SQLLite資料庫。

下面我們就依照上述的五個步驟實作看看使用 Flask 建立資料庫。

1. 匯入套件

匯入需要的套件。除了匯入flask套件(因為要使用flask來製作網站)外,我們要再匯入兩個套件,其中一個是與路徑有關的os套件,另一個是與資料庫有關的flask_sqlalchemy套件。

在這裡我們依舊使用Flask-SQLAlchemy套件來建立SQLAlchemy的資料庫。(關於ORM、SQLAlchemy相關說明可以參考這篇

import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

2. 建立資料庫路徑

在這裡我們先設定這個應用程式專案的基礎路徑(basedir)。也就是說我們要先確定整個專案放在主機的哪個路徑位置。由於現在的練習是在我們自己的電腦上執行,這個路徑即代表使用者目前開發這個專案所在的資料夾。

取得路徑的方式是使用os套件,我們使用os.path.abspath()來取得絕對的路徑位置。另外,__file__ 為目前的檔案。os.path.dirname(__file__) 是指目前檔案所在的資料夾名稱。

basedir = os.path.abspath(os.path.dirname(__file__))

這樣寫可以讓我們省去不少麻煩,我們可以不用考慮因不同作業系統而導致的路徑表示方式之間的差異。

3. 建立Flask App

接著我們建立Flask app應用。

app = Flask(__name__)

4. Configure參數設定

依SQLAlchemy的需要,設定Flask App的參數。在這裡我們需要設定兩組參數。

第一組參數要設定資料庫連線參數「SQLALCHEMY_DATABASE_URI」,也就是要設定sqlite資料庫的所在位置。在這裡,我們使用os.path.join()的方式取得資料庫的所在位置。就下面的程式碼來說,資料庫放在基礎路徑(basedir)裡面,檔名為data.sqlite。

app.config['SQLALCHEMY_DATABASE_URI']= 'sqlite:///'+os.path.join(basedir, 'data.sqlite')

如果想看其他資料庫連線的方式可以參考Flask-SQLAlchemy的說明文件:Connection URI Format

第二組參數要設定「SQLALCHEMY_TRACK_MODIFICATIONS」。這個設定如果設置為True後Flask-SQLAlchemy為追蹤各種改變的信號,這樣子會消耗額外的記憶體,官網上建議如果沒有特別需要,可設定為關閉裝態。因此,在這裡我們設定為False。

app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False

5. 傳到SQLAlchemy

最後,將Flask 的app傳到SQLAlchemy的Class類別中。在這裡我們將它指定為db變數,方便以後使用。

db = SQLAlchemy(app)

以上就是資料庫的起始設定。

實作 2:建立Model

資料庫的起始設定完畢後,接下來就是要建立Model。透過Model來規劃如何使用資料庫。

一旦建立了db物件之後,就會提供一個名為Model的類別。

1. 建立Class類別

由於Model可以用來建立建立資料庫裡面的table資料表。我們這裡設定一個Class類別,該類別的名稱就是table資料表的名稱。在這裡我們命Class類別為User。

class Users():pass

2 .類別繼承自db.Model

並且讓這個類別繼承自db.Model。這樣子我們就可以使用db.Model裡面的屬性與方法,來設定資料庫的table資料表。

class Users(db.Model):pass

3. 提供資料表的名稱(選用)

這個步驟是選用的,您不一定要執行。然而若覺得使用Users作為資料表的名稱不是您想要的,您也可以在這裡另外提供資料表的名稱。

另外提供名稱的方式如下,我們使用__tablename__來指定資料表的名稱,當然您也可以不另外提供名稱,直接使用預設值(Users):

__tablename__ = 'users'

4. 建立資料表欄位

以Class屬性的方式加上各個資料表的欄位。

id 欄位,id為primary_key。提供資料表裡面,每一筆紀錄具有獨一無二的整數識別碼,這樣子外來我們要選擇某一筆資料時,就可以憑藉著這個欄位的值來選取資料:

id = db.Column(db.Integer,primary_key=True)

name 欄位的設定如下,資料的格式是Text(文字):

name = db.Column(db.Text)

age 欄位的設定如下,資料的格式是Integer(數字):

age = db.Column(db.Integer)

5. 加上__init__與__repr__

接下來我們要加上__init__與__repr__。

__init__ 可以用來初始化Class類別;__repr__可以讓我們在使用print() 顯示我們希望Class出現的的資料。

在這裡,我們需要把 name與age放入__init__中,而id則不需要放入,因為id會自動產生。

def __init__(self, name, age):self.name = nameself.age =age

由於 __repr__可以讓我們在使用print() 顯示我們希望Class出現的的資料,我們也可以客製化顯示的資訊。

def __repr__(self):return f'使用者名稱為 {self.name} ,年齡為 {self.age} 歲。'

以上就是資料庫Model的設定,接下來就可以使用這個資料庫。

Next 接下來?

接下來該做什麼?這時候你可能會這樣子問。到目前為止我們已經建立資料庫,而且裡面已經有一個資料表名稱為users。這時候我們可開始查詢資料表的內容了吧?

暫且等等,隨然已經建立了資料表,但是目前資料表裡面是空白的狀態。我們必須先輸入一些資料才可以開始查詢。

您建立的所有Model都應該有一個構造函數。由於這個構造函數僅提供您使用,不提供SQLAlchemy使用。因此,您可以決定如何定義它們。

將資料輸入資料庫有三個步驟:

  1. 建立Python物件
  2. 將物件加入session
  3. commit這個物件

這裡所謂的session不是Flask的session,而是Flask-SQLAlchemy的。依照前述的三個步驟,它是這樣運作的:

# 1
sean = Users('Sean',39)
# 2
db.session.add(sean)
# 3
db.session.commit()

以上是我們已知的部分。

新問題的出現

當建立資料庫之後尚未初始化,或者有時候Model結構有更新時,這時你會碰到需要調整資料庫table的狀況(例如,您突然有需要增加欄位的需求),要如何更新資料庫呢?

比方說,我們若採用db.create_all建立初始資料庫,到了後來我們因為某些業務上的需要而修改欄位等結構,這時候修改的部分不會自動的對應到資料庫中,必須刪除原先建立的資料庫,然後重新再執行一次db.create_all才會重新對應。這樣子蠻麻煩的,而且資料也會被重新reset,不符合我們的需要。因此就出現了Flask Migrate這種方式。

Flask Migrate

Flask Migrate 就是在處理上面所提到的問題,透過Flask Migrate,可以將我們對Model更動過的部分migrate到資料庫的table中。

使用Flask Migrate的前提是要先透過pip 安裝 Flask-Migrate套件到我們的開發環境上。

安裝

先使用pip 安裝Flask-Migrate套件。

$ pip install Flask-Migrate

在python檔案中匯入Flask-Migrate套件:

from flask_migrate import Migrate

使用Migrate方法,第一個參數為Flask app、第二個參數為db資料庫。這樣子可以讓我們在接下來的步驟中使用command line 指令執行Flask Migrate。

Migrate(app,db)

執行指令

Flask 內建的 command line 指令,在終端機輸入指令來運行 Flask。有四個步驟需要在command line輸入(Mac OS採用export、Windows要使用set):

//MacOS/Linuxexport FLASK_APP = myapp.py//Windowsset FLASK_APP = myapp.py

其中的myapp.py是你專案中主要需要執行的Python檔案。我們使用Mac OS,所以:

1.採用下面方式輸入:

$ export FLASK_APP=base.py

2.接下來我們要輸入下面指令設定migrations資料夾:

$ flask db init

執行上面的指令後,您的專案中會出現一個migrations資料夾。

3.然後接著執行下面指令,這個指令可以設定migrations檔案,其中「-m」後面是屬於說明文字。

$ flask db migrate -m "說明文字"

4.最後,執行upgrade指令,將migrations檔案更新至資料庫中。

$ flask db upgrade

以後每次資料表有更動,就要執行以上的3、4指令。是不是覺得這個步驟與使用Git的時候很類似?

實際演練Migrate

上面大致上已經把步驟與概念都解說完畢。接著我們要實際試試看。

class Users(db.Model):        _id = db.Column('id', db.Integer, primary_key=True)   

name = db.Column('name', db.String(100))
email = db.Column(db.String(100))

def __init__(self, name, email):

self.name =name

self.email = email

上面的Users Model為上次我們建立的資料表。使用DB軟體(目前使用的是DB Browser for SQLite)打開data.sqlite,可以看到下面的資料表結構。我們可以觀察到users資料表裡面有三個欄位,欄位的類型屬性與我們在Users Model裡面設定的一樣。

我們假設這個users資料表需要增加一個mobile欄位儲存user的行動電話。這時候,我們需要在Users Model裡面增加一行欄位的敘述:

mobile = db.Column('mobile', db.String(100))

接著還需要在__init__的地方修改參數,增加與mobile相關的部分(粗體字的部分):

def __init__(self, name, email, mobile):  

self.name =name

self.email = email
self.mobile = mobile

增加完畢後,就要使用command line 指令來進行資料庫的migration。

由於我們在專案中已經有了migrations資料夾,不需要再執行flask db init。目前的狀況是users資料表有更動,因此只要執行以上的3、4兩個指令。

# 3
$ flask db migrate -m "增加mobile欄位"
#4
$ flask db upgrade

結果

再次使用DB軟體打開data.sqlite,可以看到資料表結構產生下面的變化。也就是說,我們成功的增加了一個mobile欄位。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。