说实在的真不知道该怎么起这篇文章的标题。需求是这样的,数据库的设计与定义采用sqlalchemy ORM的方式进行定义,那么如果对需要对数据库结构进行修改呢,谁家的数据库设计也不会保证一步到位啊,这种模式下是不是我要先修改ORM定义,再用一个SQL修改数据库结构,因为当我的数据库已经在运行之后,总不能再Base.metadata.create_all()了吧。alembic就是为了解决这种问题而存在的,其作者正是sqlalchemy作者本人,以前有个sqlalchemy-mirgration的项目是完成这个功能的,但是那个项目已经不更新了,所以sqlalchemy官方推荐用alembic做迁移和版本管理。
安装十分简单,因为是python官方源里的包:
pip install alembic
假设我们定义一个联系人通讯录的数据库,包括两个表,文件名为models.py:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Person(Base):
__tablename__ = 'person'
person_id = sa.Column(sa.Integer, primary_key=True)
nickname = sa.Column(sa.String(64), nullable=False)
password = sa.Column(sa.String(16))
gender = sa.Column(sa.Integer)
birthday = sa.Column(sa.Date)
class Telephone(Base):
__tablename__ = 'telephone'
tel_id = sa.Column(sa.Integer, primary_key =True)
person_id = sa.Column(sa.Integer, sa.ForeignKey('person.person_id'))
telphone_no = sa.Column(sa.String(64))
if __name__ == "__main__":
engine = sa.create_engine("sqlite:///test.db")
Base.metadata.create_all(engine)
上述程序在运行完主程序部分后会在test.db的一个sqlite数据库中自动创建两个表。
当我们用了一段时间之后,又有了新的需求,我需要存储用户的真实姓名以及身份证号、电子邮箱(每个人的电子邮箱还不只一个),那么我们首先要初始化alembic环境。
在models.py存储的路径下运行:
alembic init alembic
后一个alembic是个路径名称,是指版本管理的相关脚本存储的路径。运行完上述命令后,会在models.py存储的路径下生成一个alembic.ini的文件,和一个alembic的目录,里面有一些文件。首先修改一下alembic.ini文件,找到其中的sqlalchemy.url定义行,将其定义为你的数据库定义串:
sqlalchemy.url = sqlite:///test.db
修改alembic/env.py文件,找到target_metadata那一句前面添加如下语句,并修改target_metadata定义:
import os.path
import sys
sys.path.append(os.path.realpath('.'))
import models
target_metadata = models.Base.metadata
在person表中添加两列:
realname = sa.Column(String(64))
idcard = sa.Column(String(20))
在models.py所在目录下运行:
alembic revision --autogenerate -m 'added two columns to person table'
会得到如下消息:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'person.idcard'
INFO [alembic.autogenerate.compare] Detected added column 'person.realname'
Generating /home/lxq/python/test_alembic/alembic/versions/4624827ff13_added_two_
columns_for_person_table.py ... done
在alembic/versions目录下会生成一个文件4624827ff13_added_two_columns_for_person_table.py,内容大体能看懂,即是版本升级和降级需要的操作:
"""added two columns for person table
Revision ID: 4624827ff13
Revises:
Create Date: 2015-12-04 23:39:27.369323
"""
# revision identifiers, used by Alembic.
revision = '4624827ff13'
down_revision = None
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('person', sa.Column('idcard', sa.String(length=24), nullable=True))
op.add_column('person', sa.Column('realname', sa.String(length=64), nullable=True))
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('person', 'realname')
op.drop_column('person', 'idcard')
因为这个关于upgrade和downgrade的语句是自动生成的,因此要检查一下里面的内容,是不是你需要的东西,如果不是的话可能需要一些修改,最常见的是如果你只是想修改某一列的名字,它是检测不出来的,它会将你以前的列删除再添加一个新的列。
然后在models.py所在目录下运行:
alembic upgrade head
head是指的版本号,针对哪个版本升级数据库,也可以写上述那个versions目录下生成的那个版本号4624XXXX,如果没有重复的话可以只写前几位。head目前是指向最新的版本。我们用sqlite3 test.db打开这个数据库看一下,可以发现其表格结构已经变了,idcard和realname两个字段已经加进去了,并且还出现了一个存储当前版本号的表alembic_version。
你可以试一下,在上面的基础上再添加一个电子邮件的表格或是downgrade:
alembic downgrade 462-1
-1是指的462XXX的前一个版本,但注意的是sqlite不支持drop column,如果要试验,可以采用postgresql或是别的数据库进行试验。更详细的使用请参照官方的文档: http://alembic.readthedocs.org/en/latest/ 。