Tornado学习笔记第五篇-peewee功能介绍

上篇我们使用原生的SQL进行更新数据库,这篇我们学习下使用ORM

我们看下使用ORM的一些好处:

  1. 隔离数据库之间的差异(不在乎数据库驱动和数据类型,接口一致)
  2. 便于维护
  3. orm会提供防止sql注入等功能
  4. 变量传递式的调用更加简单

这节我们学习的ORM框架是peeweepeewee简单,灵活,申明方式和djangoorm接近。

其中async-peewee是基于asynciopeewee的异步IO库。

peeweegithub地址文档

model的定义和表的自动生成

这个小节我们将会学习如何想使用Django ORM一样由类创建成表。

如何创建一个model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from peewee import *

# 指定数据库连接 一个参数是数据库名
db = MySQLDatabase('message', host="127.0.0.1", port=3306, user="root", password="root")

class Goods(Model):
# 所有的模型都需要集成自 peewee 的 Model
name = CharField(max_length=100, verbose_name="商品名称", index=True)
click_num = IntegerField(default=0, verbose_name="点击数")
goods_num = IntegerField(default=0, verbose_name="库存数")
price = FloatField(default=0.0, verbose_name="价格")
brief = TextField(verbose_name="商品简介")

class Meta:
database = db # 指定数据库对象
table_name = "goods" # 指定表名

CharField需要指定最大长度max_length.使用ForeignKeyField建立外键关系。里面很多字段含义和django orm类似。

上面完成之后我们就可以直接创建数据表了

1
2
3
4
5
6
def init_table():
# 直接调用 db 对象的 create_tables 传入的是个可迭代对象(不一定是列表) 内容是需要创建的模型类
db.create_tables([Goods])

if __name__ == "__main__":
init_table()

我们看下生成的数据表

image-20181106181519883

虽然我们没有自己定义id为主键,peewee为我们自动添加了一个值为id的主键。而SQLAlchemy则是必须指定主键的。

我们可以创建一个基类将指定数据库连接对象放到基类中,这样其他需要指定数据库连接的只需要继承基类即可。

1
2
3
4
5
6
7
8
9
class BaseModel(Model):
add_time = DateTimeField(default=datetime.now, verbose_name="添加时间")

class Meta:
# 子类不需要设置数据库连接对象了
database = db

class Goods(BaseModel):
pass

下面我们创建具有关联关系的两个模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Supplier(BaseModel):
name = CharField(max_length=100, verbose_name="名称", index=True)
address = CharField(max_length=100, verbose_name="联系地址")
phone = CharField(max_length=11, verbose_name="联系方式")

class Meta:
database = db
table_name = "supplier"


class Goods(BaseModel):
supplier = ForeignKeyField(Supplier, verbose_name="商家", backref="goods")
name = CharField(max_length=100, verbose_name="商品名称", index=True)
click_num = IntegerField(default=0, verbose_name="点击数")
goods_num = IntegerField(default=0, verbose_name="库存数")
price = FloatField(default=0.0, verbose_name="价格")
brief = TextField(verbose_name="商品简介")

class Meta:
table_name = "goods"

创建数据表

1
2
3
4
5
6
def init_table():
db.create_tables((Goods, Supplier))


if __name__ == "__main__":
init_table()

image-20181106182401310

我们看到在数据表里保存的是supplier_id而不是supplier。我们在查询的时候如果使用supplier传入的是一个实例对象,使用supplier_id则是一个数值。这个和django的一致。

model的数据保存

在上面我们创建数据表的时候,使用了IntegerField等字段类型来指定数据类型,那peewee都有哪些字段数据类型呢,每个字段类型又有哪些属性呢?

我们可以通过查看官方文档得知:Models and Fields

通过类的实例化形式来保存数据

1
2
3
4
5
6
7
8
9
10
def save_model():
supplier = Supplier()
supplier.name = "华为"
supplier.address = "杭州"
supplier.phone = "13788745321"
supplier.save()


if __name__ == '__main__':
save_model()

直接使用save方法即可保存到数据库。

image-20181107232133826

我们看到数据库自己为我们新增了一个主键id值。

我们在调用save方法之前 实例 supplierid值是为空的,只有调用save之后才有值。这一点和其他ORM框架类似。

如果数据是字典类型,我们可以直接使用解包功能创建类实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
goods_list = [
{
"supplier": 2,
"name": "52度茅台集团国隆双喜酒500mlx6",
"click_num": 100,
"goods_num": 666,
"price": 128,
"brief": "贵州茅台酒厂(集团)保健酒业有限公司生产,是以“龙”字打头的酒水。中国龙文化上下8000年,源远而流长,龙的形象是一种符号、一种意绪、一种血肉相联的情感。"
},
{
"supplier": 3,
"name": "52度水井坊臻酿八號500ml",
"click_num": 585,
"goods_num": 288,
"price": 36,
"brief": "贵州茅台酒厂(集团)保健酒业有限公司生产,是以“龙”字打头的酒水。中国龙文化上下8000年,源远而流长,龙的形象是一种符号、一种意绪、一种血肉相联的情感。"
}
]

for data in goods_list:
good = Goods(**data)
good.save()

注意字典的键要和模型的字段值相同。

peewee查询数据

查询的官方文档:Querying

查询获取一条数据
1
2
3
4
5
6
7
# 通过 id 获取数据
good = Goods.get(Goods.id == 1)

good = Goods.get_by_id(1)

# 下面同样是通过 id 获得
good = Goods[1]

如果数据不存在,将会抛出异常

获得所有数据
1
2
3
4
goods = Goods.select()

for good in goods:
print(good.name)

上面get_by_idselect的返回值是不一样的,前者返回的数据库实例对象,是直接拼凑好SQL语句去数据库查询得到的结果。后者返回的是ModelSelect对象,不是真正数据库执行的结果。只有我们执行了下面的迭代查询之后才真正去数据库执行SQL。(底层原理是python的迭代协议)

这点和Django的查询类似。

获取指定条件的数据
1
goods = Goods.select(Goods.name, Goods.price)

如果指定了某些字段,在遍历的时候获取没有指定字段数据的时候将返回None

根据条件查询
1
2
# select * from goods where price > 100
goods = Goods.select().where(Goods.price>100)

很像原生的SQL

and条件查询

1
2
#select * from goods where price>100 and click_num>200
goods = Goods.select().where((Goods.price>100)&(Goods.click_num>200))

or条件查询

1
2
#select * from goods where price>100 or  click_num>200
goods = Goods.select().where((Goods.price>100)|(Goods.click_num>200))

like条件查询

1
2
#select * from goods where name like "%飞天"
goods = Goods.select().where(Goods.name.contains("飞天"))

这个很像Django

in条件查询

1
2
3
goods = Goods.select().where(Goods.id<<[1,3])
goods = Goods.select().where((Goods.id==1)|(Goods.id==3))
goods = Goods.select().where((Goods.id.in_([1,3])))

使用<<代表in_操作。

关于操作符的文档:Query operators

表内字段比较查询

1
2
# select * from goods where price > click_num
goods = Goods.select().where(Goods.price>Goods.click_num)

排序查询

1
2
3
4
5
6
7
#排序 select * from goods order by price desc
# 显示指定排序
goods = Goods.select().order_by(Goods.price.asc())
# 通过 + - 号
goods = Goods.select().order_by(+Goods.price)
# 默认升序
goods = Goods.select().order_by(Goods.price)

分页查询

1
2
3
#分页
goods = Goods.select().order_by(Goods.price).paginate(2, 2)
# 第一个参数是从第几个数据开始 包含 从0开始计数 第二个参数表示取多少个

官方示例文档:Query Examples

数据更新和删除
更新数据

获得对应数据,使用赋值方式更新

1
2
3
good = Goods.get_by_id(1)
good.click_num += 1
good.save()

使用更新语句

1
2
# update click_num=100 where id =1
Goods.update(click_num=Goods.click_num+1).where(Goods.id==1).execute()

这里需要我们再次调用execute去真正执行SQL,这个execute是同步操作,正是查询和拼接SQL是分离的,将peewee变成异步才有可能。

使用update更新的字段不需要指定模型

删除数据

获得想删除的数据对象,使用delete_instance删除。

1
2
good = Goods.get_by_id(1)
good.delete_instance()

使用delete语句删除数据

1
2
#delete from goods where price>150
Goods.delete().where(Goods.price>150).execute()

delete语句同样不是直接去数据库执行

通过peewee-async集成到tornado

上面学习的peewee是同步的ORM框架,如果我们想在tornado中使用,我们需要异步的ORM

peewee-async是将peewee变成异步的一个库

我们直接看下官方的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import asyncio
import peewee
import peewee_async

# Nothing special, just define model and database:
# 定义数据库连接和 peewee 一样
database = peewee_async.MySQLDatabase(
'message',
host="127.0.0.1",
port=3306,
user="root",
password="root"
)

# 创建模型和之前一样
class TestModel(peewee.Model):
text = peewee.CharField()

class Meta:
database = database

# Look, sync code is working!
# 同步的方式创建数据库
TestModel.create_table(True)
TestModel.create(text="Yo, I can do it sync!")
database.close()

# Create async models manager:
# 如果我们想异步使用 则是需要创建一个 Manager 以后执行 SQL 语句都是靠这个 Manager
objects = peewee_async.Manager(database)

# No need for sync anymore!
# 将同步禁用
database.set_allow_sync(False)

async def handler():
# 使用协程的方式来操作 进行数据库操作需要使用 我们创建的 Manager 实例
await objects.create(TestModel, text="Not bad. Watch this, I'm async!")
all_objects = await objects.execute(TestModel.select())
# objects 负责真正的执行 SQL objects.execute() 返回的是一个协程对象 一定要使用 await
for obj in all_objects:
print(obj.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(handler())
# loop.close()
# run_until_complete 执行完之后自动调用 close

# Clean up, can do it sync again:
with objects.allow_sync():
TestModel.drop_table(True)

# Expected output:
# Yo, I can do it sync!
# Not bad. Watch this, I'm async!

我们之前peewee使用很多同步方法都被peewee_async做成了协程方式,可以看下源码结构知道哪些是协程方式了。

image-20181108103106393

几篇不错的参考文章:

peewee查询

使用WTForms进行数据验证

这个小节和之前在学习Flask中使用WTForms中试一致的。

注意一点:

如果我们在tornado中直接使用wtforms的话是会报错的。

1
message_from = MessageForm(self.request.arguments)

我们可以安转一个wtforms-tornado库来解决

安装成功后将继承的基类Form换成wtforms_tornado的。

1
2
3
4
5
6
from wtforms_tornado import Form

class SumForm(Form):

a = IntegerField(validators=[Required()])
b = IntegerField(validators=[Required()])

这样直接使用self.request.arguments就不会报错了。

我们看下模板中如何使用form

1
2
3
4
5
6
7
8
9
10
11
12
13
{% autoescape None %}
{% for field in message_form %}
<span>{{ field.label.text }} :</span>
{{ field(placeholder="请输入"+field.label.text) }}

{% if field.errors %}
{% for error_msg in field.errors %}
<div class="error-msg">{{ error_msg }}</div>
{% end %}
{% else %}
<div class="error-msg"></div>
{% end %}
{% end %}
知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!