前言
最近新接手了个项目,发现关系型数据库没有做db migration,测试环境里需要使用命令行或者图形化界面登上去用软件手动建表。为了让自己未来工作的更轻松点我还是决定要花上点时间解决这个问题。
问题描述
问题也就是关系型数据库里有不少已经弄好的表,但是这些表都是以前通过sql或者手动建好的,为了能够把alembic加进去,首先需要为已存在的表建立一个初始的脚本,当然这件事不会是手动来做,我们需要用脚本实现它。
解决方案
alembic在生成新的revision时,提供了一个--autogenerate参数,命令如下:
alembic revision --autogenerate -m "your commit here"
此时需要在alembic的env.py文件中指定一个metadata,作为配置的context传进去,这个metadata时是一个sqlalchemy.schema.MetaData的实例,描述了当前数据库的表结构。然后alembic会自动对比脚本所记录的版本和metadata里提供的数据结构的差别,并自动生成新的migration脚本。
实际上alembic的官方文档里对如何从已存在的数据库里直接生成初始的migration脚本说的也不太清楚,经过仔细研究了下alembic和sqlalchemy的文档和搜索,要按照下面的方法来做:
- 使用sqlalchemy读取已存在数据库的数据结构,需要使用sqlalchemy.schema.MetaData.reflect方法,可以获得已存在数据结构的metadata。
- 修改项目中alembic/env.py文件,将metadata设置为上一步中获得metadata。
- 使用alembic revision --autogenerate命令来生成migration脚本。
具体实施
首先为项目添加alembic功能,进入项目目录输入命令:
alembic init alembic
此时,alembic为该项目生成了相应的文件,具体结构如下:
yourproject/
alembic/
env.py
README
script.py.mako
versions/
3512b954651e_add_account.py
2b1ae634e5cd_add_order_id.py
3adcc9a56557_rename_username_field.py
接下来要使用sqlalchemy来获取当前数据库结构吧。我写了如下脚本:
# existing_db.py
from sqlalchemy import *
from sqlalchemy.schema import *
engine = create_engine('driver://user:pass@localhost/dbname')
metadata = MetaData(engine)
metadata.reflect() # env.py 需要引入这个metadata
此时,metadata中就保留了当前数据库的表结构信息了。
然后需要在almebic/env.py中引入这个metadata才能自动生成migration脚本。如下所示
# env.py
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
# import sys
# sys.path.append('path-to-existing_db.py') 可能需要将existing_db.py添加到python的系统路径下,否则不一定能import到exisiting_db.py
import existing_db
target_metadata = existing_db.metadata
接下来就可以运行
alembic revision --autogenerate -m "your commit here"
然后我们就可以在项目alembic/versions/目录下找到新生成的migration脚本了。
结论
其实还是第一次遇到这种情况,所谓的接手项目就是这么尴尬,刚好其实还是很容解决的,总共从研究到跑通也就不到一个小时而已,实际上搞通了操作一次也就两分钟,为了以后的幸福,花这点时间还是值得的。