Alembic 簡明教程

本文記錄了Alembic的主要使用過程。
數據庫版本化

通常我們會將我們的代碼放入到某個VCS(版本控制系統)中,進行可追溯的版本管理。一個項目除了代碼,通常還會有一個數據庫,這個數據庫可能會隨着項目的演進發生變化,甚至需要可以回滾到過去的某個狀態,於是一些工具將數據庫的版本化也納入了管理。

Alembic 是 Sqlalchemy 的作者實現的一個數據庫版本化管理工具,它可以對基於Sqlalchemy的Model與數據庫之間的歷史關係進行版本化的維護。
Alembic

你可以通過 pip install alembic 直接安裝,它需要三個依賴包,PIP會自動處理。

    SQLAlchemy 同作者的ORM工具
    Mako 同作者的模版工具
    MarkupSafe 轉換Markup到HTML的組件

初始化

在你的項目根目錄運行

alembic init YOUR_ALEMBIC_DIR

隨後你的項目目錄應該會新增一個alembic.ini文件以及一個YOUR_ALEMBIC_DIR目錄,最好指定一個符合自己項目風格的命名。

接下來的操作都是圍繞這個目錄。

yourproject/
    alembic.ini
    YOUR_ALEMBIC_DIR/
        env.py
        README
        script.py.mako
        versions/
            3512b954651e_add_account.py
            2b1ae634e5cd_add_order_id.py
            3adcc9a56557_rename_username_field.py

    alembic.ini 提供了一些基本的配置
    env.py 每次執行Alembic都會加載這個模塊,主要提供項目Sqlalchemy Model 的連接
    script.py.mako 遷移腳本生成模版
    versions 存放生成的遷移腳本目錄

除了基本的Alembic項目之外,你還可以指定幾個特殊的項目模版。

$ alembic list_templates
Available templates:
generic - Generic single-database configuration.
multidb - Rudimentary multi-database configuration.
pylons - Configuration that reads from a Pylons project environment.
Templates are used via the 'init' command, e.g.:
  alembic init --template pylons ./scripts

你需要編輯alembic.ini文件去指定Alembic的數據庫連接。

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of auto generate
# revision_environment = false
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

版本

首先創建一個基本數據庫版本,你當然可以從已有的數據庫出發。

$ alembic revision -m "create account table"
Generating /path/to/yourproject/YOUR_ALEMBIC_DIR/versions/1975ea83b712_create_accoun
t_table.py...done

生成的版本文件類似於:

"""create account table
Revision ID: 1975ea83b712
Revises: None
Create Date: 2011-11-08 11:40:27.089406
"""
# revision identifiers, used by Alembic.
revision = '1975ea83b712'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
    pass
def downgrade():
    pass

其中 revision = '1975ea83b712'和down_revision = None指定了這個reversion的當前版本號,以及父版本號,就是通過這個進行追溯。

然後我們修改upgrade和downgrade進行實際的升降級操作。通過易用的API,我們只需要對op和sa對象進行操作即可。

def upgrade():
    op.create_table(
        'account',
        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('account')

具體op所支持的操作請看操作API引用。
升級,降級

然後我們更新好最新的版本。

$ alembic upgrade head
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade None -> 1975ea83b712

一般我們需要指定版本號進行升級,但是對於最新以及最初版本有兩個額外的別名,head指最新版本,base指最初的版本。

降級也很簡單,只要upgrade以及downgrade實現足夠魯棒。

$ alembic downgrade base
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running downgrade 1975ea83b712 -> None

自動生成遷移腳本

Alembic 不僅僅能夠維護數據庫歷史版本,而且帶來這個新奇的特性,自動生成遷移腳本。

通常我們進行編碼時候,在確定需求後,通常需要對數據模型進行變化。
不要擔心,只要不是出於技術實現問題,諸如長度過少數據類型應用錯等。而是因爲業務的變更而導致的數據模型變更完全可以理解。"程序=算法+數據結構“,業務邏輯發生了變化,實現算法、數據結構必然發生變化,不要貪圖抽象隔離設計之類云云,這類除了使得程序複雜度增加之外,很難保證未來預期是否如之前所設計的工作量完全可以避免。

口頭商量,確定方案,然後開始打算修改,除了設計稿之外,往往最先實現的是ORM中我們的實體類,因爲他們簡單易懂。
在此例中我因爲需要給用戶添加微博OAuth2.0的綁定,所以新增了兩個字段:

class User(Base):
    # …. origin others setting
    weibo_token = Column(String(64), nullable=True)
    weibo_expires = Column(DateTime, nullable=True)

這個時候直接運行程序必然會失敗,因爲映射關係已經被打亂了,我們需要重建這個關係。

配置YOUR_ALEMBIC_DIR/env.py文件,修改target_metadata = None爲你的元信息對象。
這裏我就直接導入了,但是在導入的過程中由於目錄問題,所以一個額外的Hack。

# Hack for model import
import os
import sys
sys.path.insert(0, os.path.realpath("."))
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import my model
# target_metadata = None
from model import Base
target_metadata = Base.metadata

見證奇蹟的時刻:

$ alembic revision --autogenerate -m "add weibo token fields for user"
INFO [alembic.migration] Context impl MySQLImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate] Detected added column 'user.weibo_expires'
INFO [alembic.autogenerate] Detected added column 'user.weibo_token'
  Generating migrate/versions/3117ba3f1f1f_add_weibo_token_expires_for_user.py...done

剩下的就可以按照正常的Alembic操作進行升降級了,是不是把煩人的數據庫結構變更問題解決了?當然,這些問題可能在其他生態圈中根本不是問題(Java/Hibernate,Ruby/RoR)。 別忘了將這些變更提交到VCS中,他們可是非常值得你去維護。
自動遷移腳本生成功能實現

你可以在Alembic的源碼的autogenerate.py模塊中找到,一共近七百行。
程序流程如下: d.png

寫得挺流水的,沒有抽象,至少_compare_*都只是對於兩個集合差集的提取,以及交集的迭代,這部分是可以很容易抽象出來的。
得到差異後,再對差異數據進行“渲染”,既有原地執行,也有渲染輸出爲Sql,還能對升降級進行不同的輸出。
Sqlalchemy-migrate

Sqlalchemy-migrate 借鑑RoR的思路,對數據庫進行版本化管理。但是隻是提供了基本的功能,對於多個數據庫引擎的差異暴露給了開發者,不易用。
命令行的控制細節也挺多了,和Sqlalchemy似乎不是一個思路。 這裏就有一個StackOverflow的用戶在抱怨難以上手。

alembic revision --autogenerate -m "add machine state"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章