flask_migrate实现ORM映射数据库管理
在使用SQLAlcjemy的ORM时,想必大家都遇到过数据库模型更新后无法直接修改至数据库上,必须要将数据库删掉然后才可以将新的表结构映射到数据库中,但是酱汁是有点坑啊,如果业务已经上线运行了一段时间了,这个时间删库删表,简直吊炸天啊。于是乎我们怎么解决这类问题呢?那就看看flask_migrate的功能吧。
flask_migrate简单来说就相当于把orm和数据库的关系映射提取了出来,通过shell的方式操作orm控制数据库。
常用的三个参数如下
init 创建一个新的迁移存储库
migrate 生成一个新的修订版本
upgrade 升级到最新的修订版本
下面我们来看下使用
首先贴一份全代码
#coding:utf-8
from flask_script import Manager
#导入flask_script模块,由于我们是在命令行下控制数据库的映射关系建立,所以嘛要依赖这个插件
from flask_sqlalchemy import SQLAlchemy
#这个ORM,必须导入咯
from flask import Flask
from flask_migrate import Migrate,MigrateCommand
#导入flask_migrate中的Migrate模块(用于初始化app)和MigrateCommand命令模块(用于提供命令行命令)
import os
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SECRET_KEY'] = 'zheshiyigekey'
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///' + os.path.join(basedir, 'data2.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=True
db = SQLAlchemy(app)
manager = Manager(app)
#绑定app到Manager上,实现manager.run启动
migrate = Migrate(app,db)
#绑定app和db到migrate上,实现ORM通过migrate管理
manager.add_command('db',MigrateCommand)
#给manager添加MigrateCommand相关的控制命令,父命令为db
#例如python sqldb.py db init
class Article(db.Model):
__tablename__='article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(64),nullable=False)
content = db.Column(db.String(64),nullable=False)
# content2 = db.Column(db.String(64))
# 注释掉一行,模拟稍后的数据表结构修改
def __repr__(self):
return "<%s,%s,%s>"%(self.id,self.title,self.content)
@app.route('/inster/')
def inster():
#inster
article1 = Article(title='aaa',content='bbb')
article2 = Article(title='ccc', content='ddd')
db.session.add(article1)
db.session.add(article2)
db.session.commit()
return 'inster'
@app.route('/select/')
def select():
#select
# result = Article.query.filter(Article.title == 'aaa').all() #查询所有符合条件的数据
result = Article.query.all()
# result = Article.query.filter(Article.title == 'aaa').first() #查询符合条件的第一条数据,如果没有数据则返回None
# for i in result:
# print i.id
# print i.title
# print i.content
print result
return 'select'
@app.route('/update/')
def update():
#update
article1 = Article.query.filter(Article.title == 'aaa').first()
print article1
print article1.title
article1.title = 'new title'
db.session.commit()
return "update"
@app.route('/delete/')
def delete():
#delete
article1 = Article.query.filter(Article.title == 'aaa').first()
db.session.delete(article1)
db.session.commit()
return "delete"
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
# app.run(host='0.0.0.0', port=8000, debug=True)
manager.run()
拿到代码后需要先创建下数据库表
创建新的迁移存储库
(venv) [root@9aa3b82f4987 my_test]# python sqldb.py db init
Creating directory /data/my_test/migrations ... done
Creating directory /data/my_test/migrations/versions ... done
Generating /data/my_test/migrations/README ... done
Generating /data/my_test/migrations/alembic.ini ... done
Generating /data/my_test/migrations/env.py ... done
Generating /data/my_test/migrations/script.py.mako ... done
Generating /data/my_test/migrations/env.pyc ... done
Please edit configuration/connection/logging settings in '/data/my_test/migrations/alembic.ini' before proceeding.
#执行后会生成一个migrations的目录,我们可以通过tree看到里面的结构
(venv) [root@9aa3b82f4987 my_test]# tree migrations/
migrations/
├── alembic.ini
├── env.py
├── env.pyc
├── README
├── script.py.mako
└── versions
1 directory, 5 files
生成一个新的修订版本
(venv) [root@9aa3b82f4987 my_test]# python sqldb.py db migrate
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'article'
Generating /data/my_test/migrations/versions/33a330224a8f_.py ... done
(venv) [root@9aa3b82f4987 my_test]# tree migrations/
migrations/
├── alembic.ini
├── env.py
├── env.pyc
├── README
├── script.py.mako
└── versions
├── 33a330224a8f_.py
└── 33a330224a8f_.pyc
1 directory, 7 files
创建到数据库(由于这里使用的是sqlit所以没法直观的看到创建好的表结构,大家可以通过连接mysql看效果)
(venv) [root@9aa3b82f4987 my_test]# python sqldb.py db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 33a330224a8f, empty message
创建完成后就可以启动服务了
启动sqldb脚本
python sqldb.py runserver -p 8000 -h 0.0.0.0
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)
#首次访问url http://x.x.x.x:8000/select/ 可以看到控制台的输出是个空列表
[]
#访问下inster页面 http://x.x.x.x:8000/inster/
#再次访问select页面 http://x.x.x.x:8000/select/ 可以看到控制台输出了数据库的查询结果
[<1,aaa,bbb>, <2,ccc,ddd>]
以上的测试表示当前的数据库已经可以使用了
尝试修改表结构
修改表结构
class Article(db.Model):
__tablename__='article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(64),nullable=False)
content = db.Column(db.String(64),nullable=False)
content2 = db.Column(db.String(64)) #将代码中注释的这行取消注释
def __repr__(self):
return "<%s,%s,%s>"%(self.id,self.title,self.content)
重启程序python sqldb.py runserver -p 8000 -h 0.0.0.0
访问查询页面
OperationalError: (sqlite3.OperationalError) no such column: article.content2
[SQL: u'SELECT article.id AS article_id, article.title AS article_title,
article.content AS article_content, article.content2 AS article_content2
\nFROM article'] (Background on this error at: http://sqlalche.me/e/e3q8)
访问后提示异常,我们看下异常点,提示我们没有content2列,那是因为我们改了映射关系,
但是数据库中没有更新表结构,所以无法查询成功
通过migrate将新修改的表结构更新到数据库
(venv) [root@9aa3b82f4987 my_test]# python sqldb.py db migrate
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'article.content2'
Generating /data/my_test/migrations/versions/218f86ca1785_.py ... done
(venv) [root@9aa3b82f4987 my_test]# tree migrations/
migrations/
├── alembic.ini
├── env.py
├── env.pyc
├── README
├── script.py.mako
└── versions
├── 218f86ca1785_.py
├── 218f86ca1785_.pyc
├── 33a330224a8f_.py
└── 33a330224a8f_.pyc
1 directory, 9 files
(venv) [root@9aa3b82f4987 my_test]# python sqldb.py db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade 33a330224a8f -> 218f86ca1785, empty message
再次启动程序
重启程序python sqldb.py runserver -p 8000 -h 0.0.0.0
访问查询页面http://x.x.x.x:8000/select/
终端返回正常的查询结果[<1,aaa,bbb>, <2,ccc,ddd>]