CH5 Section 5: Be careful with SQLAlchemy, or, how I'm stupid as hell.
Watch Out, Climate Has Changed... (by 重塑雕像的权利)
本文分两部分:
第一部分是吐槽 Flask-SQLAlchemy,第二部分是 关于《Flask web 开发》这本书视图函数中操作数据库的内容并分享本人踩坑案例。 好吧,其实这个大坑是我自己给自己挖的。。
第一部分:
首先其实按照此书前四章来操作其实是没太大问题的,但从第五章的数据库开始,就有点绕了。
简单粗暴来句总结:如果有一个问题,你想到可以用SQLAlchemy 和ORM来解决,那么现在你有三个问题了。
作为一名曾经深陷于SQLAlchemy和Flask-SQLAlchemy之间理不清剪还乱的暧昧关系而不知频频给自己挖坑的小白用户,我对此是有切肤之痛的。
我之所以在SQLite和SQLAlchemy之间选择了 “SQLAlchemy,的Flask-SQLAlchemy”来做数据库,起因是我轻信了这么一句话:
可用 Flask-SQLAlchemy 这个模块简化操作。
看到了没??简化操作!
简化操作
四个大字闪闪发光!!!比撒满碎芝士的大理石重芝士蛋糕切片还要充满诱惑力!! 但这其实是个大坑!!!
因为,你要好好区分“Flask 中 SQLAlchemy “以及“Flask 中 SQLAlchemy 的 Flask-SQLAlchemy 这个模块”!!
注意,我上面说的 SQLAlchemy 并不是 SQLAlchemy ,而是“Flask 中 SQLAlchemy 的 Flask-SQLAlchemy 这个模块”,所以我被坑惨了!!我一会看 SQLAlchemy 教程,一会看 Flask-SQLAlchemy 这个模块的教程...一会class ,一会basedir一会create_engine...为什么初始化数据库有完全不同的代码啊!为什么一边是db.session,一边是con.execute!!!
然后,SQLAlchemy的开发者是要多想炫耀自己记忆力有多好?你非要用10个字母又大写又小写来命名,并且还要弄个好几个长得差不多但其实每个脾气都不一样暗藏杀机的 ”sqlalchemy.ext.declarative “ 和 “ flask_sqlalchemy ”以及 “SQLAlchemy” 到底是想怎样? import 个 “declarative_base”到底又是搞什么鬼??这个单词有几个字母我数来数去都数不清~~~
还有:SQL 抽象层这个概念也别混淆了。 比如你要加数据入库,你应该用 “flask_sqlalchemy ” 的
db.session.add(xxxx1)
db.session.add(xxxx2)
而不是:
con = engine.connect()
con.execute(weatherapi.insert(xx = 'data[0]'cxx = 'data[1]'))
因为上面这个是SQL 抽象层的做法。在这个之前你要在代码里上头先 import:
from sqlalchemy import create_engine, MetaData
然后:
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
metadata = MetaData(bind=engine)
什么鬼??这些都是个啥???人工对象关系映射?Are you fooooking kidding me ?? 这成吨成吨的玩意直接砸过来,有没考虑我的感受? 开发sqlalchemy 的程序员你必须比 Sherlock Holmes 还要 High Functioning Sociopath. 所以这就是当时为什么我有一种想高高的站在阳台上的强烈感觉.....
总而言之吧,一定要擦亮眼睛,SQLAlchemy !这个名字起这么长就明显不怀好意!!!
第二部分:视图函数中操作数据库
下面分享一段本人如何在视图函数中操作数据库的踩坑经历:
实现目的:我就是想写一个函数: 用户在客户端(浏览器)输入查询数据,存储数据到表里。
至于如何在视图函数中用sqlalchemy 操作数据库,也就是数据库/客户端(浏览器)输入和获取数据后如何处理之间的关系,如此重要的事项,书里只轻描淡写来了一段:
"示例 5 5 hello.py : 在视图函数中操作数据库 -提交表单后 ,程序会使用 f i l t e r _ b y ( )查询过滤器在数据库中查找提交的名字 。变量 k n o w n被写入用户会话中 ,因此重定向之后 ,可以把数据传给模板 ,用来显示自定义的欢迎消息 。"
什么?这就没了? 真的没了。
所以,如果你不想再命令端直接操作,如何写函数去获取客户端输入的数据,还是有得折腾的。
根据官方文档关于 SQLAlchemy 插入数据库表里的行数据的说明:
Inserting data into the database is a three step process:
Create the Python object Add it to the session Commit the session
@app.route('/adduser')
def add_user():
user1 = User('ethan', '[email protected]')
user2 = User('admin', '[email protected]')
db.session.add(user1)
db.session.add(user2)
考虑到 直接用 @app.route 把路径直接写入很容易造成混乱局面,不能直接参考上面的版本。
解决办法如下:
要从 web.py(处理逻辑的并且能够调用html模版在浏览器显示的那个文档whatever you named it —原谅我这么表述)import 个函数进来,然后把这个函数收到的数据赋予新函数的某个值,so , import 个什么函数进来嘛到底? 到底是要怎样才能接到 web.py 这个文档里 “@app.route('/',methods=['GET', 'POST']) ” POST 之后那个数据?
答:答案可以简单得令人失望:
step 1: 将你那个web.py(处理逻辑的并且能够调用html模版在浏览器显示的那个文档whatever you named it) import那个获取数据的函数进来;
step 2: 创建表并创建下面这个函数,注意,在此假设你的表名为 abc , 表的结构每一行有 4个元素,如a b c d. data 为你在web.py 里获取的数据(记得区分是 list 还是 dict,还是一个包含了 dict 的list,或是包含了 list 的 dict,以及是否为元组等) 你应该这么去写函数获取数据:
def insertxxxx(data): # xxxx为数据库里表名
a = data[0]
b = data[1]
c = data[2]
d = data[3]
w = tt(1,a,b,c,d)
#关于这个神奇的 “1”稍后说明
db.session.add(w)
db.session.commit()
db.session.close()
关于神奇的“1”的,因为我是仿照书上例子来写,并且发现它会主动进行排序,也就是 "1 a b c d " "2 a b c d"... 一开始我以为是系统主动排序,但 faketooth 是这么解答“1”的存在以及合理性的:
表中数据的顺序基于数据插入的顺序而已。 因为id这个列设置的属性(primary_key=True),每条数据插入的时候,如果不指定SQLAlchemy会自动生成一个。 主键的特性就是不为空,不重复,而不重复的最简单的实现方式就是递增。 所以,只是看起来像排过序了。 实际上,不会有哪个数据库会提供把表里的数据自动排序的功能。一方面这么做没必要,另一方面会造成很大的系统开销,这样的话,这种数据库产品生存下来的概率就会很小。 想要快速查询数据库中某条单独的数据,是没必要把数据按顺序存储的。有其他相关的数据结构和技术来解决这个问题,而且效果非常好。
关键的话来了:
“在没有足够多的背景信息下,认为这是SQLAlchemy强制要求即可。”
嗯,那就接受下来吧。 最后祝大家蹚坑愉快!!我在前面等着你们!!
(20170222如果再掉坑我再来补充。。。)