注意:强烈建议构建新应用的开发者使用 NDB 客户端库,它与 DB 客户端库相比具有多项优势,例如可通过 Memcache API 进行自动实体缓存。如果您当前使用的是较早的 DB 客户端库,请参阅 DB 到 NDB 的迁移指南
概览
数据存储区实体有一个键和一组属性。应用使用数据库存储区 API 定义数据模型,并创建要存储为实体的模型的实例。模型可为 API 所创建的实体提供通用结构,并可以定义用于验证属性值的规则。
Model 类
Model 类
应用描述它与模型配合使用的数据的类型。模型是继承自 Model 类的 Python 类。模型类定义了数据存储区实体的新种类,以及该种类将采用的属性。种类名称由继承自 db.Model
。
Model 属性 (property) 是使用模型类中的类特性 (attribute) 定义的。每个类特性 (attribute) 均是 Property 类的一个子类实例,通常是某个提供的 Property 类。Property 实例保留了属性的配置,如实例是否必须要有该属性才有效,或用于实例的默认值(如果未提供)。
from google.appengine.ext import db class Pet(db.Model): name = db.StringProperty(required=True) type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"])) birthdate = db.DateProperty() weight_in_pounds = db.IntegerProperty() spayed_or_neutered = db.BooleanProperty()
在 API 中,某个定义的实体种类的实体用对应的模型类的实例表示。应用可以通过调用类的构造函数来创建新实体。应用使用实例的属性来访问和控制实体的属性。模型实例构造函数接受属性的初始值作为关键字参数。
from google.appengine.api import users pet = Pet(name="Fluffy", type="cat") pet.weight_in_pounds = 24
注意:Model 类的特性 (attribute) 是模型属性 (property) 的配置,其值为 Property 实例。模型实例的属性是实际属性值,其值为 Property 类接受的类型。
模型类会使用 Property 实例来验证分配给模型实例属性的值。当第一次构建模型实例时以及为实例属性分配新值时,都会进行属性值验证。这确保了属性绝不会有无效值。
由于在构建实例时要进行验证,因此配置为必需属性的任何属性都必须在构造函数中进行初始化。在本示例中,name
和 type
是必需值,因此它们的初始值是在构造函数中指定的。weight_in_pounds
不是模型所必需的,所以它在开始时未被分配值,到后面才分配了值。
在第一次“放置”使用构造函数创建的模型实例之前,数据存储区中没有该实例。
注意:如同所有 Python 类属性一样,模型属性配置是在第一次导入脚本或模块时初始化的。由于 App Engine 会缓存在两次请求之间导入的模块,因此模块配置可能在某个用户的请求期间进行初始化,并在另一个用户的请求期间重复使用。请勿用请求或当前用户特定的数据初始化模型属性配置(例如默认值)。请参阅应用缓存了解详情。
Expando 类
使用 Model 类定义的模型建立一组固定的属性,该类的每个实例都必须具有这些属性(可能有默认值)。这是一种非常有用的对数据对象进行建模的方法,但是数据存储区并不要求指定类型的每个实体都有相同的属性集。
有时,实体有一些非必要属性(例如其他同类实体的属性),其实是非常有用的。在 Datastore API 中这种实体由“expando”模型表示。Expando 模型类是 Expando 父类的子类。分配给 Expando 模型实例属性的任何值都将成为数据存储区实体的属性,并使用该属性的名称。这些属性称为动态属性。类属性中使用 Property 类实例定义的属性为固定属性。
Expando 模型可以拥有固定属性和动态属性。模型类仅使用固定属性的 Property 配置对象来设置类属性。应用为动态属性分配值时将创建动态属性。
class Person(db.Expando): first_name = db.StringProperty() last_name = db.StringProperty() hobbies = db.StringListProperty() p = Person(first_name="Albert", last_name="Johnson") p.hobbies = ["chess", "travel"] p.chess_elo_rating = 1350 p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"] p.travel_trip_count = 13
由于动态属性没有模型属性定义,所以动态属性未经验证。任何动态属性的值可以为任何 Datastore 基类型,其中包括 None
。两个同类实体的相同动态属性可以有不同的值类型,并且可以有一个不设置属性,而另一个设置。
与固定属性不同,动态属性无需存在。值为 None
的动态属性与不存在的动态属性不同。如果 Expando 模型实例没有某一属性的特性,则对应的数据实体将没有该属性。您可以通过删除该特性来删除动态属性。
名称以下划线 (_
) 开头的属性不会保存到数据存储区实体中。这样一来,您便可以在模型实例上存储值以供临时内部使用,而不会影响与实体一起保存的数据。
注意:静态属性将始终保存到 Datastore 实体中,无论该属性是 Expando、Model,还是以下划线 (_
) 开头。
del p.chess_elo_rating
如果某个查询在过滤条件中使用了动态属性,那么它将只返回其属性值与查询中使用的值的类型相同的实体。同样,该查询只返回设置了该属性的实体。
p1 = Person() p1.favorite = 42 p1.put() p2 = Person() p2.favorite = "blue" p2.put() p3 = Person() p3.put() people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50) # people has p1, but not p2 or p3 people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50) # people has no results
注意:上面的示例跨多个实体组使用查询,这样可能会返回过时的结果。如需获得高度一致的结果,请在实体组中使用祖先查询。
Expando 类是 Model 类的子类,且继承了它的所有方法。
PolyModel 类
Python API 包含另一个用于数据建模的类,您可以通过它定义类的层次结构,并执行可以返回指定类或其任一子类的实体的查询。此类模型和查询称为“多态”,因为它们允许一个类的多个实例作为父类查询的结果。
下面的示例定义了 Contact
类,以及子类 Person
和 Company
:
from google.appengine.ext import db from google.appengine.ext.db import polymodel class Contact(polymodel.PolyModel): phone_number = db.PhoneNumberProperty() address = db.PostalAddressProperty() class Person(Contact): first_name = db.StringProperty() last_name = db.StringProperty() mobile_number = db.PhoneNumberProperty() class Company(Contact): name = db.StringProperty() fax_number = db.PhoneNumberProperty()
此模型确保所有 Person
实体和所有 Company
实体都具有 phone_number
和 address
属性,并且查询 Contact
实体可以返回 Person
或 Company
实体。只有 Person
实体具有 mobile_number
属性。
子类可以与任何其他模型类一样实例化:
p = Person(phone_number='1-206-555-9234', address='123 First Ave., Seattle, WA, 98101', first_name='Alfred', last_name='Smith', mobile_number='1-206-555-0117') p.put() c = Company(phone_number='1-503-555-9123', address='P.O. Box 98765, Salem, OR, 97301', name='Data Solutions, LLC', fax_number='1-503-555-6622') c.put()
查询 Contact
实体可以返回 Contact
、Person
或 Company
的实例。以下代码将打印以上所创建的两个实体的信息:
for contact in Contact.all(): print 'Phone: %s\nAddress: %s\n\n' % (contact.phone_number, contact.address)
查询 Company
实体只能返回 Company
的实例:
for company in Company.all() # ...
目前,多态模型不应直接传递给 Query 类构造函数。相反,它应使用 all()
方法,如上面示例所示。
如需详细了解如何使用多态模型及其实现方式,请参阅 PolyModel 类。
Property 类和类型
数据存储区支持一组固定的实体属性的值类型,包括 Unicode 字符串、整数、浮点数、日期、实体键、字节字符串 (blob) 和各种 GData 类型。每个数据存储区值类型都有 google.appengine.ext.db
模块提供的对应 Property 类。
类型和 Property 类描述了所有受支持的值类型及其对应的 Property 类。下面介绍几种特殊的值类型。
字符串和 Blob
数据存储区支持两种用于存储文本的值类型:长度最多为 1500 个字节的短文本字符串和长度最多为 1 兆字节的长文本字符串。短字符串编入索引,可以在查询过滤条件和排序顺序中使用。长字符串不编入索引,不能在过滤条件或排序顺序中使用。
短字符串值可以是 unicode
值或 str
值。如果值是 str
,则假设编码为 'ascii'
。如需为 str
值指定一种不同的编码,可以用 unicode()
类型的构造函数将其转换为 unicode
值,这种类型的构造函数会使用 str
和编码名称作为参数。短字符串可通过 StringProperty 类进行建模。
class MyModel(db.Model): string = db.StringProperty() obj = MyModel() # Python Unicode literal syntax fully describes characters in a text string. obj.string = u"kittens" # unicode() converts a byte string to a Unicode string using the named codec. obj.string = unicode("kittens", "latin-1") # A byte string is assumed to be text encoded as ASCII (the 'ascii' codec). obj.string = "kittens" # Short string properties can be used in query filters. results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")
长字符串值由 db.Text 实例表示。其构造函数采用 unicode
值或 str
值,并可能会使用 str
中使用的编码名称。长字符串可通过 TextProperty 类进行建模。
class MyModel(db.Model): text = db.TextProperty() obj = MyModel() # Text() can take a Unicode string. obj.text = u"lots of kittens" # Text() can take a byte string and the name of an encoding. obj.text = db.Text("lots of kittens", "latin-1") # If no encoding is specified, a byte string is assumed to be ASCII text. obj.text = "lots of kittens" # Text properties can store large values. obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")
数据存储区还支持两种类似类型的非文本字节字符串:db.ByteString 和 db.Blob。这些值是原始字节的字符串,不作为编码文本(如 UTF-8)进行处理。
与 db.StringProperty
值类似,db.ByteString
值也会编入索引。与 db.TextProperty
属性类似,db.ByteString
的值限制为 1500 个字节。ByteString
实例代表短字节字符串,并使用 str
值作为构造函数的参数。字节字符串通过 ByteStringProperty 类进行建模。
与 db.Text
类似,db.Blob
值最大可达一兆字节,但不会编入索引,并且不能在查询过滤器或排序顺序中使用。db.Blob
类使用一个 str
值作为其构造函数的参数,或者您可以直接分配该值。Blob 通过 BlobProperty 类进行建模。
class MyModel(db.Model): blob = db.BlobProperty() obj = MyModel() obj.blob = open("image.png").read()
列表
属性可以有多个值,在数据存储区 API 中表示为 Python list
。列表可以包含数据存储区支持的任何值类型的值。一个列表属性甚至可以有不同类型的值。
由于顺序通常会得到保留,因此当查询和 get() 返回实体时,列表属性值的顺序与存储时的顺序相同。有一个例外:Blob
和 Text
值将移到列表的末尾;不过,它们仍保留着相对于彼此的原来顺序。
ListProperty 类对列表进行建模,并强制列表中的所有值都为给定类型。为方便起见,该库还提供了 StringListProperty(类似于 ListProperty(basestring)
)。
class MyModel(db.Model): numbers = db.ListProperty(long) obj = MyModel() obj.numbers = [2, 4, 6, 8, 10] obj.numbers = ["hello"] # ERROR: MyModel.numbers must be a list of longs.
列表属性上的查询使用过滤条件逐个测试列表中的每个值。仅当列表中的某个值符合该属性的所有过滤条件时,该实体才会与查询匹配。如需了解详情,请参阅数据存储区查询页面。
# Get all entities where numbers contains a 6. results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6") # Get all entities where numbers contains at least one element less than 10. results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")
查询过滤条件仅对列表成员执行操作。无法在一个查询过滤条件中测试两个列表的相似之处。
数据存储区在内部将列表属性值表示为该属性的多个值。如果列表属性值为空列表,则该属性在数据存储区中没有表示。对于静态属性(具有 ListProperty)和动态属性,数据存储区 API 处理这种情况的方式不同:
- 可以为静态 ListProperty 分配空列表作为值。该属性不存在于数据存储区中,但是模型实例的行为就像值是空列表一样。静态 ListProperty 不能有
None
值。 - 无法为具有
list
值的动态属性分配空列表值。但是,它可以有None
值,并可以删除(使用del
)。
ListProperty 模型测试添加到列表中的值是否为正确类型,如果类型不正确,则会引发 BadValueError。即使在检索先前存储的实体并将其加载到模型中时,也会进行此测试(可能会失败)。由于 str
值在存储前转换为 unicode
值(作为 ASCII 文本),因此 ListProperty(str)
会视为 ListProperty(basestring)
,即接受 str
和 unicode
值的 Python 数据类型。您还可以使用 StringListProperty()
实现此目的。
如需存储非文本字节字符串,请使用 db.Blob 值。当存储和检索 blob 字符串的字节时,将保留这些字节。您可以将形式为 Blob 列表的属性声明为 ListProperty(db.Blob)
。
列表属性可以采用不同寻常的方式与排序顺序进行交互;请参阅 Datastore 查询页面了解详情。
引用
一个属性值可以包含另一个实体的键。该值是 Key 实例。
ReferenceProperty 类对键值进行建模,并强制所有值都引用给定种类的实体。为方便起见,该库还提供了 SelfReferenceProperty,相当于 ReferenceProperty,它引用具有该属性的实体的同一种类实体。
为 ReferenceProperty 属性分配模型实例时,系统将自动使用其键作为值。
class FirstModel(db.Model): prop = db.IntegerProperty() class SecondModel(db.Model): reference = db.ReferenceProperty(FirstModel) obj1 = FirstModel() obj1.prop = 42 obj1.put() obj2 = SecondModel() # A reference value is the key of another entity. obj2.reference = obj1.key() # Assigning a model instance to a property uses the entity's key as the value. obj2.reference = obj1 obj2.put()
ReferenceProperty 属性值可以用作引用实体的模型实例。如果引用的实体不在内存中,则使用该属性作为实例时,系统将自动从数据存储区中提取实体。ReferenceProperty 还存储了一个键,但使用该属性会导致载入相关实体。
obj2.reference.prop = 999 obj2.reference.put() results = db.GqlQuery("SELECT * FROM SecondModel") another_obj = results.fetch(1)[0] v = another_obj.reference.prop
如果某个键指向一个不存在的实体,访问该属性将引发错误。如果某个应用程序预计引用可能无效,则可以使用 try/except
块测试对象是否存在:
try: obj1 = obj2.reference except db.ReferencePropertyResolveError: # Referenced entity was deleted or never existed.
ReferenceProperty 有另外一个便利的功能:反向引用。当模型具有另一个模型的 ReferenceProperty 时,每个引用的实体都会获得一个值为 Query 的属性,该属性会返回引用它的第一个模型的所有实体。
# To fetch and iterate over every SecondModel entity that refers to the # FirstModel instance obj1: for obj in obj1.secondmodel_set: # ...
反向引用属性的名称默认为 modelname_set
(其中模型类的名称采用小写字母,“_set”添加到末尾),可通过 ReferenceProperty 构造函数的 collection_name
参数进行调整。
如果有多个引用同一模型类的 ReferenceProperty 值,反向引用属性的默认结构将引发错误:
class FirstModel(db.Model): prop = db.IntegerProperty() # This class raises a DuplicatePropertyError with the message # "Class Firstmodel already has property secondmodel_set" class SecondModel(db.Model): reference_one = db.ReferenceProperty(FirstModel) reference_two = db.ReferenceProperty(FirstModel)
如需避免这种错误,您必须明确地设置 collection_name
参数:
class FirstModel(db.Model): prop = db.IntegerProperty() # This class runs fine class SecondModel(db.Model): reference_one = db.ReferenceProperty(FirstModel, collection_name="secondmodel_reference_one_set") reference_two = db.ReferenceProperty(FirstModel, collection_name="secondmodel_reference_two_set")
模型实例的自动引用和取消引用、类型检查和反向引用仅在使用 ReferenceProperty 模型属性类时可用。存储为 Expando 动态属性值或 ListProperty 值的键不具有这些特性。