快速入门

本文档简要概述了 Peewee 的主要功能。本指南将涵盖

注意

如果您想要更深入的内容,这里有一个关于使用 peewee 和 Flask 框架创建“twitter”风格的 Web 应用程序的完整教程。在项目 examples/ 文件夹中,您可以找到更多自包含的 Peewee 示例,例如 博客应用程序

强烈建议打开一个交互式 shell 会话并运行代码。这样,您可以了解如何输入查询。

模型定义

模型类、字段和模型实例都映射到数据库概念

对象

对应于...

模型类

数据库表

字段实例

表上的列

模型实例

数据库表中的行

在使用 peewee 启动项目时,通常最好从数据模型开始,方法是定义一个或多个 Model

from peewee import *

db = SqliteDatabase('people.db')

class Person(Model):
    name = CharField()
    birthday = DateField()

    class Meta:
        database = db # This model uses the "people.db" database.

注意

Peewee 会根据类的名称自动推断数据库表名称。您可以通过在内部“Meta”类中指定 table_name 属性(与 database 属性一起)来覆盖默认名称。要了解有关 Peewee 如何生成表名称的更多信息,请参阅 表名称 部分。

另请注意,我们将模型命名为 Person 而不是 People。这是一个您应该遵循的惯例——即使表将包含多个人,我们始终使用单数形式命名类。

有很多 字段类型适合存储各种类型的数据。Peewee 处理在pythonic 值和数据库使用的值之间进行转换,因此您可以在代码中使用 Python 类型,而无需担心。

当我们使用 外键关系在模型之间建立关系时,事情变得有趣。使用 peewee,这很简单

class Pet(Model):
    owner = ForeignKeyField(Person, backref='pets')
    name = CharField()
    animal_type = CharField()

    class Meta:
        database = db # this model uses the "people.db" database

现在我们有了模型,让我们连接到数据库。虽然不必显式打开连接,但这是一个好习惯,因为它会立即显示数据库连接中的任何错误,而不是在稍后的某个任意时间执行第一个查询时显示。完成时关闭连接也很有好处 - 例如,Web 应用程序在收到请求时可能会打开连接,并在发送响应时关闭连接。

db.connect()

我们将首先在数据库中创建用于存储数据的表。这将创建具有适当列、索引、序列和外键约束的表

db.create_tables([Person, Pet])

存储数据

让我们首先向数据库中填充一些人。我们将使用 save()create() 方法来添加和更新人员记录。

from datetime import date
uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15))
uncle_bob.save() # bob is now stored in the database
# Returns: 1

注意

调用 save() 时,将返回已修改的行数。

你还可以通过调用 create() 方法来添加一个人,该方法将返回一个模型实例

grandma = Person.create(name='Grandma', birthday=date(1935, 3, 1))
herb = Person.create(name='Herb', birthday=date(1950, 5, 5))

要更新一行,请修改模型实例并调用 save() 来保留更改。在这里,我们将更改奶奶的名字,然后将更改保存到数据库中

grandma.name = 'Grandma L.'
grandma.save()  # Update grandma's name in the database.
# Returns: 1

现在我们已在数据库中存储了 3 个人。让我们给他们一些宠物。奶奶不喜欢家里的动物,所以她不会有任何动物,但赫伯是一个动物爱好者

bob_kitty = Pet.create(owner=uncle_bob, name='Kitty', animal_type='cat')
herb_fido = Pet.create(owner=herb, name='Fido', animal_type='dog')
herb_mittens = Pet.create(owner=herb, name='Mittens', animal_type='cat')
herb_mittens_jr = Pet.create(owner=herb, name='Mittens Jr', animal_type='cat')

在漫长的一生之后,米顿斯病倒并死亡。我们需要将其从数据库中删除

herb_mittens.delete_instance() # he had a great life
# Returns: 1

注意

delete_instance() 的返回值是从数据库中删除的行数。

鲍勃叔叔决定赫伯家的动物死亡太多,所以他收养了菲多

herb_fido.owner = uncle_bob
herb_fido.save()

检索数据

我们数据库的真正优势在于它允许我们通过查询检索数据。关系数据库非常适合进行临时查询。

获取单个记录

让我们从数据库中检索奶奶的记录。要从数据库中获取单个记录,请使用 Select.get()

grandma = Person.select().where(Person.name == 'Grandma L.').get()

我们还可以使用等效的简写 Model.get()

grandma = Person.get(Person.name == 'Grandma L.')

记录列表

让我们列出数据库中的所有人

for person in Person.select():
    print(person.name)

# prints:
# Bob
# Grandma L.
# Herb

让我们列出所有猫及其主人的姓名

query = Pet.select().where(Pet.animal_type == 'cat')
for pet in query:
    print(pet.name, pet.owner.name)

# prints:
# Kitty Bob
# Mittens Jr Herb

注意

上一个查询存在一个大问题:因为我们正在访问 pet.owner.name,并且我们没有在原始查询中选择此关系,peewee 必须执行一个附加查询来检索宠物的所有者。此行为称为 N+1,通常应避免。

有关使用关系和联接的深入指南,请参阅 关系和联接 文档。

我们可以通过选择 PetPerson 以及添加一个 join 来避免额外的查询。

query = (Pet
         .select(Pet, Person)
         .join(Person)
         .where(Pet.animal_type == 'cat'))

for pet in query:
    print(pet.name, pet.owner.name)

# prints:
# Kitty Bob
# Mittens Jr Herb

让我们获取 Bob 拥有的所有宠物

for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
    print(pet.name)

# prints:
# Kitty
# Fido

我们可以在此执行另一项操作来获取 Bob 的宠物。由于我们已经有一个对象来表示 Bob,因此我们可以执行此操作

for pet in Pet.select().where(Pet.owner == uncle_bob):
    print(pet.name)

排序

让我们通过添加 order_by() 子句来确保按字母顺序排序

for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
    print(pet.name)

# prints:
# Fido
# Kitty

让我们现在列出所有人员,从最年轻到最年长

for person in Person.select().order_by(Person.birthday.desc()):
    print(person.name, person.birthday)

# prints:
# Bob 1960-01-15
# Herb 1950-05-05
# Grandma L. 1935-03-01

组合筛选表达式

Peewee 支持任意嵌套表达式。让我们获取生日为以下日期的所有人员

  • 1940 年之前(祖母)

  • 1959 年之后(bob)

d1940 = date(1940, 1, 1)
d1960 = date(1960, 1, 1)
query = (Person
         .select()
         .where((Person.birthday < d1940) | (Person.birthday > d1960)))

for person in query:
    print(person.name, person.birthday)

# prints:
# Bob 1960-01-15
# Grandma L. 1935-03-01

现在让我们做相反的事情。生日在 1940 年至 1960 年之间(包括这两年)的人

query = (Person
         .select()
         .where(Person.birthday.between(d1940, d1960)))

for person in query:
    print(person.name, person.birthday)

# prints:
# Herb 1950-05-05

聚合和预取

现在让我们列出所有人员以及他们拥有的宠物数量

for person in Person.select():
    print(person.name, person.pets.count(), 'pets')

# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets

我们再次遇到了 N+1 查询行为的经典示例。在这种情况下,我们为原始 SELECT 返回的每个 Person 执行一个附加查询!我们可以通过执行 JOIN 并使用 SQL 函数来聚合结果来避免这种情况。

query = (Person
         .select(Person, fn.COUNT(Pet.id).alias('pet_count'))
         .join(Pet, JOIN.LEFT_OUTER)  # include people without pets.
         .group_by(Person)
         .order_by(Person.name))

for person in query:
    # "pet_count" becomes an attribute on the returned model instances.
    print(person.name, person.pet_count, 'pets')

# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets

注意

Peewee 提供了一个神奇的帮助器 fn(),可用于调用任何 SQL 函数。在上面的示例中,fn.COUNT(Pet.id).alias('pet_count') 将转换为 COUNT(pet.id) AS pet_count

现在让我们列出所有人员及其所有宠物的名称。正如你可能猜到的那样,如果我们不小心,这很容易变成另一个 N+1 情况。

在深入研究代码之前,请考虑此示例与我们列出所有宠物及其所有者名称的早期示例有何不同。宠物只能有一个所有者,因此当我们从 PetPerson 执行联接时,总会有一个匹配项。当我们从 PersonPet 执行联接时,情况会有所不同,因为一个人可能没有宠物,也可能有多个宠物。因为我们正在使用关系数据库,所以如果我们要从 PersonPet 执行联接,那么每个拥有多个宠物的人都会重复一次,每个宠物重复一次。

它看起来像这样

query = (Person
         .select(Person, Pet)
         .join(Pet, JOIN.LEFT_OUTER)
         .order_by(Person.name, Pet.name))
for person in query:
    # We need to check if they have a pet instance attached, since not all
    # people have pets.
    if hasattr(person, 'pet'):
        print(person.name, person.pet.name)
    else:
        print(person.name, 'no pets')

# prints:
# Bob Fido
# Bob Kitty
# Grandma L. no pets
# Herb Mittens Jr

通常这种类型的重复是不可取的。为了适应更常见(且直观)的列出一个人并附加一个列表该人的宠物的工作流,我们可以使用一种称为 prefetch() 的特殊方法

query = Person.select().order_by(Person.name).prefetch(Pet)
for person in query:
    print(person.name)
    for pet in person.pets:
        print('  *', pet.name)

# prints:
# Bob
#   * Kitty
#   * Fido
# Grandma L.
# Herb
#   * Mittens Jr

SQL 函数

最后一个查询。这将使用 SQL 函数来查找所有名称以大写或小写G 开头的人

expression = fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'
for person in Person.select().where(expression):
    print(person.name)

# prints:
# Grandma L.

这只是基础知识!您可以根据需要使查询变得复杂。查看 查询 中的文档以获取更多信息。

数据库

我们已经完成了数据库,让我们关闭连接

db.close()

在实际应用程序中,有一些已建立的模式,用于管理数据库连接的生命周期。例如,Web 应用程序通常会在请求开始时打开连接,并在生成响应后关闭连接。连接池 可以帮助消除与启动成本相关的延迟。

要了解如何设置数据库,请参阅 数据库 文档,其中提供了许多示例。Peewee 还支持 在运行时配置数据库 以及随时设置或更改数据库。

使用现有数据库

如果您已经有一个数据库,则可以使用 pwiz(一个模型生成器) 自动生成 peewee 模型。例如,如果我有一个名为charles_blog 的 postgresql 数据库,我可能会运行

python -m pwiz -e postgresql charles_blog > blog_models.py

下一步是什么?

快速入门指南到此结束。如果您想查看完整的 Web 应用程序,请查看 示例应用程序