本文记录了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"
数据库版本化
通常我们会将我们的代码放入到某个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"