records包是为了让人们更加方便的使用数据库的中的数据,简洁但强大。
import records
db = records.Database('postgres://...')
rows = db.query('select * from active_users')
rows返回的是一个迭代器
>>> rows[0]
<Record {"username": "model-t", "active": true, "name": "Henry Ford", "user_email": "model-t@gmail.com", "timezone": "2016-02-06 22:28:23.894202"}>
也可以使用更加优雅的方式读取数据
row.user_email, row['user_email'] 或者是 row[3]
或者是将数据全部取出来
>>> rows.all()
[<Record {"username": ...}>, <Record {"username": ...}>, <Record {"username": ...}>, ...]
rows还贴心的把数据变成字典
rows.as_dict() 或 rows.as_dict(ordered=True)
records包支持将数据导出成各种格式
csv tsv json yaml html xls xlsx dbf latex ods
>>> print(rows.export('csv'))
username,active,name,user_email,timezone
model-t,True,Henry Ford,model-t@gmail.com,2016-02-06 22:28:23.894202
...
核心类有三个 Record, RecordCollection, Database。在做源码分析时,先从入口类Database开始:
Database类的核心是sqlalchemy框架,初始化时会根据给定的数据库url,调用create_engine方法,连接数据库。在Database还实现了__enter__和__exit__方法,以方便使用上下文管理器语法。Database类的核心方法是query,其他的query_file, bulk_query_file都是基于它做的。bulk_query 和 transaction 直接调用了sqlalchemy的excute和begin方法。
query方法中首先调用了execute返回一个游标cursor,再继而使用了Record和RecordCollection,构成了类似于迭代器的chain。
def query(self, query, fetchall=False, **params):
"""Executes the given SQL query against the Database. Parameters
can, optionally, be provided. Returns a RecordCollection, which can be
iterated over to get result rows as dictionaries.
"""
# Execute the given query.
cursor = self.db.execute(text(query), **params) # TODO: PARAMS GO HERE
# Row-by-row Record generator.
row_gen = (Record(cursor.keys(), row) for row in cursor)
# Convert psycopg2 results to RecordCollection.
results = RecordCollection(row_gen)
# Fetch all results if desired.
if fetchall:
results.all()
return results
Record类使用cursor方法返回的key和value,也就是列名和相应的数据做进一步的处理。为了更方便的使用,Record类的__getitem__实现了序列协议,__getattr__实现了基于__getitem__实现了动态属性。 as_dict方法实现了元组到字典的转换,实现思路值得参考:
def as_dict(self, ordered=False):
"""Returns the row as a dictionary, as ordered."""
items = zip(self.keys(), self.values())
return OrderedDict(items) if ordered else dict(items)
dataset使用了property装饰器将方法变成了属性,其中使用records包作者开发的tablib包,并且使用方法_reduce_datetimes将datetime类型转换成iso的字符串格式。同理将数据导出成json,csv等格式也是复用了tablib的代码。
def _reduce_datetimes(row):
"""Receives a row, converts datetimes to strings."""
row = list(row)
for i in range(len(row)):
if hasattr(row[i], 'isoformat'):
row[i] = row[i].isoformat()
return tuple(row)
RecordCollection类和Record类大部分方法相似,但是实现了__iter__和__next__的迭代器协议。这是为了尽量不一次性将数据读入内存,在需要的时候才进行计算。