[Day 17] 資料庫 (四):用 Alembic 做資料庫初始化與 Migration

有時候,我們需要在資料庫內先存放一些基本資料,才比較方便我們後續使用 (或進行測試),這個過程我們就稱為「初始化 (Initialization)」。

建立空資料庫其實也應該算做初始化的一部分

資料庫初始化

要在資料庫先存放資料本身並不難,其實簡單一點的作法就是使用昨天提到的 CRUD。只要在啟動 FastAPI 時檢查資料庫內是否有資料 (或是是否含有某一筆預設資料),如果沒有,就執行事先寫好的操作資料庫的腳本,讓 SQLAlchemy 幫我們新增資料到資料庫中,就能做到初始化的效果。

這樣的做法在小專案或沒那麼複雜的專案裡是沒有問題的,但如果初始化的狀態比較複雜,後續在版本控制或是誤刪初始資料時,有可能導致再度初始化。因此,保險一點的作法是引入版本控制的概念,將空資料庫視作第一版,而資料庫初始化後的結果視作第二個版本,並將這個初始化流程視作版本更新 (更貼切一點的說法是 migration)。

我一直找不到 Migration 的理想中文翻譯,我覺得翻作「遷移」有點怪

想要做資料庫的版本控制,FastAPI 官網的建議是使用 Alembic

FastAPI 也是建議使用 Alembic 來進行資料庫初始化 (連結)

Alembic 是什麼?

Alembic 是 SQLAlchemy 作者開發做為資料庫 Migration 的工具,本身與 FastAPI 是獨立的 (換句話說,就算沒有安裝 FastAPI 也可以用 Alembic 幫 SQLAlchemy 做 migration)。(Github) (文件)

大家可以去看看這篇文章,它對如何用 Alembic 做 migration 有很詳細的教學

Alembic

安裝

首先,要先安裝 Alembic

1
pip install alembic

Alembic 初始化

接下來,我們需要進行設定,因此用下面這行指令來產生設定檔

1
alembic init testAlembic

其中,init 後面的參數是資料夾名稱,可以自行決定。

輸入完之後就會發現新增了一個 testAlembic 資料夾 (裡面的 versions 資料夾是空的),以及 alembic.ini

接著打開 alembic.ini,找到 sqlalchemy.url,將它改成我們在 database.py 內所設定的資料庫連線 URL,也就是 sqlite:///./test.db
(預設是 driver://user:pass@localhost/dbname )

忘記資料庫連線 URL 的人可以去看 [Day 14] 的介紹

建立 migration script

設定完成後就可以開始建立 migration script 了。

Alembic 有提供手動和自動建立的方法,我們介紹手動的部份就好,自動的部份可以看上面的教學文章或是官方文件

我覺得沒有很複雜的話,用手動建立就好,速度不會差太多。但如果真的很複雜,我也會怕自動建立的過程出錯,還是要反覆檢查,因此我自己都是用手動建立的 XD

手動建立的指令如下

1
alembic revision -m "create user table"

其中 -m 後面的參數就是放這次更新的說明 (別寫太長,它會成為檔名 XD)

可以看到 versions 資料夾內多了一個檔案 b6f25e44ac91_create_user_table.py

Alembic 在每個版本都會自動建立一個 ID 做為版本號 (這次是 b6f25e44ac91)

這是裡面預設的內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""create user table

Revision ID: b6f25e44ac91
Revises:
Create Date: 2023-09-30 22:54:28.736201

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'b6f25e44ac91'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
pass


def downgrade() -> None:
pass

接下來就要來手動調整 upgrade()downgrade() 內的內容了。

這邊我們用 Alembic 官網範例

1
2
3
4
5
6
7
8
9
10
def upgrade():
op.create_table(
'testuser',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('name', sa.String(50), nullable=False),
sa.Column('description', sa.Unicode(200)),
)

def downgrade():
op.drop_table('testuser')

不小心再用了一個 user,所以表格名稱改成 testuser XD

執行 migration script

接著在 terminal 輸入 alembic upgrade head

成功之後,打開 test.db,就會發現多了兩個表格,一個是剛剛在腳本裡寫的 testuser,另一個是 Alembic 用來記錄當前版本用的 alembic_version

查看當前版本

此時,可以再回到 terminal 輸入 alembic current 來查看當前本

其他常用指令

alembic downgrade -1
降一個版本,也可以用 -2 降兩個版本 (以此類推),如果不想數要講幾個版本的話,也可以直接輸入版本號

alembic history
查看各版本的資訊,加上 --verbose 則可以看到詳細資訊

重點回顧

今天主要是介紹 Alembic 的使用,包含了

  1. 安裝設定
  2. 手動建立 migration script
  3. 執行 migration

資料庫的部份就先告一個段落了,原本還想介紹動態生成表格的做法 (無法事先知道完整的表格格式),但找不到好地方放進去,因此只好先捨棄掉。

關鍵字:MetaData

明天會開始討論錯誤處理~