基于SQLAlchemy构建精简的API
SQLAlchemy是一个强大的数据库操作工具,也是目前Python的ORM实际上的标准。它的便利性主要体现在以下三点:
- 它支持大多数的主流数据库,如SQLite,Oracle,MySQL,Microsoft SQL server等,为不同的数据库提供了统一的接口。
- 它提供了一种面向对象的基于SQL语言的抽象(也就是ORM),这使得我们操作数据库时候,可以写出更干净,也更Pythonic的代码。免除了SQL语言的学习成本,也避免了将SQL代码和Python代码混合起来写所带来的复杂度。
- 对于底层性能的优化(如数据库连接池的创建),SQLAlchemy都会自动完成。
问题的由来
虽然SQLAlchemy很好很强大,可是有时候我也会感到它的繁琐之处。比如要完成一项查询,查找Dataset对象中的两列:id和name,过滤条件是id列的值小于100:
results=DBSession.query(Datasets).filter(Datasets.id<100).all() map(lambda x:[x.id,x.name],results)
虽然只有两行,可是冗余的部分还是有的,我尝试将不变的部分剥离出来,构建了一个精简化的函数:
def meta_API(ORM_entity,selector,filterer):
if isinstance (ORM_entity,list):
return map(selector,
filterer(DBSession.query(*ORM_entity)))
else:
return map(selector,
filterer(DBSession.query(ORM_entity)))
这样就把selector(选择器)和filterer(过滤器)这两个过程抽象了出来,于是可以将前面对Datasets对象的查询过程改成:
meta_API(ORM_entity=Datasets,
selector=lambda x:[x.id,x.name],
filterer=lambda x:x.filter(Datasets.id<100).all())
到底是看上去更清晰了,还是更古怪了,这倒是一个见仁见智的问题。
构建更高层的API
以meta_API为接口,可以写出更多的语法糖,比如dump(输出全部),head(输出首行),search(按表达式搜索)以及joint(连接表)等,如下:
def meta_dump_API(ORM_entity,selector,filterer=lambda x:x):
"""
Return all the result after filtering and selecting.
The function is a syntactic sugar derived from meta_API. You can skip `.all()` in the filterer.
"""
return meta_API(ORM_entity=ORM_entity,
filterer=lambda x:filterer(x).all(),
selector=selector)
def meta_search_API(ORM_entity,selector,predictives):
"""
Return all the result matches the predictives after selecting.
The function is a syntactic sugar derived from meta_API. You can use predictives instead of filterer to skip `.filter().all()`.
"""
if isinstance (ORM_entity,list):
return meta_API(ORM_entity=ORM_entity,
filterer=lambda x:x.filter(*predictives).all(),
selector=selector)
else:
return meta_API(ORM_entity=ORM_entity,
filterer=lambda x:x.filter(predictives).all(),
selector=selector)
def meta_head_API(ORM_entity,selector,filterer=lambda x:x):
"""
Return the first result after filtering and selecting.
The function is a syntactic sugar derived from meta_API. You can skip `.one()` in the filterer.
"""
return meta_API(ORM_entity=ORM_entity,
selector=selector,
filterer=lambda x:filterer(x).one())
def meta_joint_API(ORM_entities, filterer, selector,
inner=Dataset,outter=None):
"""
Return the result after joining,filtering and selecting.
The function is a syntactic sugar derived from meta_API. You can skip `.join()` in the filterer.
"""
return meta_API(ORM_entity=[inner]+ORM_entities,
selector=lambda x:selector(x[1:]),
filterer=lambda x:filterer(x.join(outter)))
可以看出,meta_API这一抽象还是有其实用性的。
定制API
最后就可以根据特定数据库的数据结构自己定制API了,以我们实验室的内部数据库为例:
from sqlalchemy import engine_from_config
from settings import DATABASE_CONFIG
engine=""
def initDB():
global engine;
engine=engine_from_config(DATABASE_CONFIG,"")
DBSession.configure(bind=engine)
def dump_CellLine(filterer=lambda x:x):
return meta_dump_API(ORM_entity=CellLine,
selector=lambda x:(x.id,x.name),
filterer=filterer, )
def head_CellLine():
return meta_head_API(CellLine,
lambda x:(x.id,x.name))
def joint_CellLine(selector=lambda):
return meta_joint_API(ORM_entities=[CellLine,Dataset],
selector=lambda x:x,
filterer=lambda x:x.all(),
inner=Dataset,
outter=CellLine)
注:Dataset中需要加ForeignKey参数
class Dataset(Base):
__tablename__='mytable_datasets'
id = Column(Integer, primary_key=True)
cell_line_id = Column(Integer,ForeignKey("mytable_celllines.id"))
感觉joint的那个API设计得还是很繁琐,不过我目前只能想到这么多。




