实体属性参考

Datastore 模式的 Firestore (Datastore) 支持多种属性值数据类型。其中包括:

  • 整数
  • 浮点数
  • 字符串
  • 日期
  • 二进制数据

如需查看类型的完整列表,请参阅 属性和值类型

属性和值类型

与实体关联的数据值由一个或多个属性构成。 每个属性都有一个名称和一个或多个值。属性可具有多个类型的值,且两个实体的同一属性可具有不同类型的值。属性可以编入索引,也可以不编入索引(对属性 P 排序或过滤的查询会忽略未将 P 编入索引的实体)。一个实体最多可以有 20,000 个编入索引的属性。

支持以下值类型:

如果查询涉及的属性具有混合类型的值,Datastore 会根据内部表示法确定排序方式:

  1. Null 值
  2. 定点数
    • 整数
    • 日期和时间
  3. 布尔值
  4. 字节序列
    • Unicode 字符串
    • Blobstore 键
  5. 浮点数
  6. Datastore 键

因为 长文本字符串和长字节字符串 未编入索引,因此它们没有定义排序顺序。

属性类型

NDB 支持以下属性类型:

属性类型 说明
IntegerProperty 64 位有符号整数
FloatProperty 双精度浮点数
BooleanProperty 布尔值
StringProperty Unicode 字符串;最多 1500 个字节,编入索引
TextProperty Unicode 字符串;长度无限,不编入索引
BlobProperty 未解析的字节字符串:
如果设置 indexed=True,最多 1500 个字节,编入索引;
如果 indexedFalse(默认),长度无限,不编入索引。
可选的关键字参数:compressed
DateTimeProperty 日期和时间(请参阅日期和时间属性
DateProperty 日期(请参阅日期和时间属性
TimeProperty 时间(请参阅日期和时间属性
GeoPtProperty 地理位置。这是一个 ndb.GeoPt 对象。该对象具有 latlon 属性(两者均为浮点数)。您可以使用两个浮点数构建一个这样的对象,例如 ndb.GeoPt(52.37, 4.88);或使用字符串构建,例如 ndb.GeoPt("52.37, 4.88")。(这实际与 db.GeoPt 是同一个类)
KeyProperty Datastore 键
可选的关键字参数:kind=kind,要求分配给此属性的键始终具有所指示的类。可能是一个字符串或一个 Model 子类。
BlobKeyProperty Blobstore 键
相当于旧版 db API 中的 BlobReferenceProperty,但属性值是 BlobKey 而不是 BlobInfo;您可以使用 BlobInfo(blobkey) 从 BlobKey 来构建 BlobInfo
UserProperty 用户对象。
StructuredProperty 按照值将一种模型包含进另一种(请参阅结构化属性
LocalStructuredProperty StructuredProperty 类似,但磁盘上的表示法是不透明的 blob,并且不编入索引(请参阅结构化属性)。
可选的关键字参数:compressed
JsonProperty 值是一个 Python 对象(如列表、字典或字符串),可使用 Python 的 json 模块进行序列化;Datastore 将 JSON 序列化存储为 blob。默认情况下不编入索引。
可选的关键字参数:compressed
PickleProperty 值是一个 Python 对象(如列表、字典或字符串),可使用 Python 的 pickle 协议进行序列化;Datastore 将 pickle 序列化存储为 blob。默认情况下不编入索引。
可选的关键字参数:compressed
GenericProperty 通用值
主要由 Expando 类使用,但也可以显式使用。其类型可以是 intlongfloatboolstrunicodedatetimeKeyBlobKeyGeoPtUserNone
ComputedProperty 值由用户定义的函数根据其他属性计算得出。(请参阅计算型属性。)

其中一些属性具有可选的关键字参数 compressed。如果属性具有 compressed=True,则其数据在磁盘上会使用 gzip 压缩。其占用的空间更少,但需要 CPU 在读写操作时进行编码/解码。

压缩和解压缩都是“懒惰”操作,压缩的属性值只有在您第一次访问时才会解压缩。如果您读取一个含有压缩属性值的实体,并在未访问压缩属性的情况下将该实体写回,则该属性不会发生解压缩和压缩操作。上下文缓存也参与此懒惰方案,但 memcache 始终存储压缩属性的压缩值。

由于压缩需要额外的 CPU 时间,通常最佳做法是只有当数据太大必须压缩时才会使用压缩属性。请记住,基于 gzip 的压缩通常对图像和其他媒体数据无效,因为这些格式已经使用特定媒体压缩算法(例如,用于图像的 JPEG)压缩过。

属性选项

大多数属性类型都支持一些标准参数。第一个参数是用于指定属性的 Datastore 名称的可选位置参数。从应用的角度考虑,您可以使用此参数给予属性一个不同的 Datastore 名称。在 Datastore 中,此参数常用于缩小占用空间,既可让 Datastore 使用缩写的属性名称,又可让您的代码使用更长、更有意义的属性名称。例如,

class Employee(ndb.Model):
    full_name = ndb.StringProperty('n')
    retirement_age = ndb.IntegerProperty('r')

这对预计每个实体有多个值的重复属性来说特别有用。

此外,大多数属性类型都支持以下关键字参数:

参数 类型 默认值 说明
indexed bool 通常为 True 在 Datastore 的索引中包括属性;如果为 False,则无法查询值,但写入速度会更快。并非所有属性类型都支持索引;对于这些属性将 indexed 设置为 True 会失败。
未编入索引的属性需要的写入操作次数比编入索引的属性少。
repeated bool False 属性值是一个包含基础类型值的 Python 列表(请参阅重复属性)。
不能与 required=Truedefault=True 结合使用。
required bool False 属性必须有一个指定的值。
default 属性的基础类型 如果未明确指定,则为属性的默认值。
choices 基础类型值的列表 None 允许的值的可选列表。
validator 函数 None 用于验证且可能强制转换值的可选函数。将使用参数 (prop, value) 调用,并且应返回(可能是强制的)值或引发异常。对强制性值再次调用该函数不应进一步修改该值。(例如,返回 value.strip()value.lower() 是可以接受的,但返回 value + '$' 是不可接受的。)还可能返回 None,这意味着“没有变化”。另请参阅写入属性子类
verbose_name 字符串 None 可选的 HTML 标签,用于 jinja2 等 Web 表单框架。

重复属性

任何具有 repeated=True 的属性都是重复属性。该属性给出基础类型值(而不是单个值)的列表。例如,使用 IntegerProperty(repeated=True) 定义的属性值是整数列表。

Datastore 可以查看此类属性的多个值。系统会为每个值创建单独的索引记录。这会影响查询语义;请参阅查询重复属性,查看示例。

此示例使用重复属性:

class Article(ndb.Model):
    title = ndb.StringProperty()
    stars = ndb.IntegerProperty()
    tags = ndb.StringProperty(repeated=True)
...
article = Article(
    title='Python versus Ruby',
    stars=3,
    tags=['python', 'ruby'])
article.put()

这会创建一个 Datastore 实体,其中包含以下内容:

assert article.title == 'Python versus Ruby'
assert article.stars == 3
assert sorted(article.tags) == sorted(['python', 'ruby'])

查询 tags 属性时,此实体将满足 'python''ruby' 查询。

在更新重复属性时,您可以为其分配新列表或更改现有列表。当您分配新列表时,列表项的类型会立即得到验证。无效项类型(例如,为上述 art.tags 分配 [1, 2])会引发异常。当您更改列表时,更改的内容不会立即得到验证。相反,当您将实体写入 Datastore 时,值会得到验证。

Datastore 在重复属性中保留了列表项的顺序,因此您可以为其排序方式指定某种含义。

日期和时间属性

有三种属性类型可用于存储与日期和时间相关的值:

  • DateProperty
  • TimeProperty
  • DateTimeProperty

这些属性类型获取的值属于标准 Python datetime 模块的对应类(datetimedatetime)。这三种属性类型中最常见的是用于表示日历日期和一天中时间的 DateTimeProperty,其他两种属性类型偶尔用于仅需要日期(出生日期)或时间(如会议时间)的特殊目的。因技术原因,DatePropertyTimeProperty 都是 DateTimeProperty 的子类,但您不应依赖此继承关系(请注意它不同于 datetime 模块本身定义的基础类之间的继承关系)。

这些属性每个都有两个额外的布尔值关键字选项:

选项 说明
auto_now_add 创建实体时,将属性设置为当前日期/时间。您可以手动替换此属性。更新实体时,该属性不会更改。如果需要在更新时更改属性,请使用 auto_now
auto_now 创建和任何时候更新实体时,将属性设置为当前日期/时间。

这些选项不能与 repeated=True 搭配使用。这两个选项默认为 False;如果这两者都设置为 True,则 auto_now 具有优先权。可以替换 auto_now_add=True 的属性值,但不能替换 auto_now=True 的属性值。写入实体后,才会生成自动值;也就是说,这些选项不提供动态默认值。(这些细节与旧的 db API 不同。)

结构化属性

您可以构建模型的属性。例如,您可以定义一个含有地址列表的模型类 Contact,每个地址都有其内部结构。 结构化属性(类型为 StructuredProperty``)可用于执行此操作,例如:

class Address(ndb.Model):
    type = ndb.StringProperty()  # E.g., 'home', 'work'
    street = ndb.StringProperty()
    city = ndb.StringProperty()

...

class Contact(ndb.Model):
    name = ndb.StringProperty()
    addresses = ndb.StructuredProperty(Address, repeated=True)

...

guido = Contact(
    name='Guido',
    addresses=[
        Address(
            type='home',
            city='Amsterdam'),
        Address(
            type='work',
            street='Spear St',
            city='SF')])

guido.put()

这会创建一个具有以下属性的 Datastore 实体:

assert guido.name == 'Guido'
addresses = guido.addresses
assert addresses[0].type == 'home'
assert addresses[1].type == 'work'
assert addresses[0].street is None
assert addresses[1].street == 'Spear St'
assert addresses[0].city == 'Amsterdam'
assert addresses[1].city == 'SF'

读回此类实体将完全重建原始的 Contact 实体。 虽然 Address 实例是使用模型类所用的语法定义的,但它们并不是完整实体。它们在 Datastore 中没有自己的键,也无法独立于其所属的 Contact 实体而被检索。不过,应用可以查询其各个字段的值;请参阅筛选结构化属性值。 请注意,从 Datastore 的角度来看,address.typeaddress.streetaddress.city 被视为并行数组,但 NDB 库隐藏了这一方面并构建 Address 实例的对应列表。

您可以为结构化属性指定常用属性选项indexed 除外)。在这种情况下,Datastore 名称是第二个位置参数(第一个是用于定义子结构的模型类)。

如果您不需要查询子结构的内部属性,则可以改用本地结构化属性 (LocalStructuredProperty)。如果您将上述示例中的 StructuredProperty 替换为 LocalStructuredProperty,则 Python 代码的行为是相同的,但 Datastore 只会看到每个地址的不透明 blob。示例中创建的 guido 实体将按如下方式存储:

name = 'Guido'
address = <opaque blob for {'type': 'home', 'city': 'Amsterdam'}>
address = <opaque blob for {'type': 'work', 'city': 'SF',
                              'street': 'Spear St'}>

该实体将被正确读回。由于此类型的属性始终不会编入索引,因此您无法查询地址值。

计算属性

计算属性 (ComputedProperty) 是只读属性,其值是由应用提供的函数通过其他属性值计算得出的。请注意,计算属性仅支持通用属性支持的类型!计算值将写入 Datastore 中,以便该值可在 Datastore 查看器中查询和显示。但是当从 Datastore 中读回该实体时,存储值会被忽略。确切地说,每当请求该值时,都会调用函数重新计算。例如:

class SomeEntity(ndb.Model):
    name = ndb.StringProperty()
    name_lower = ndb.ComputedProperty(lambda self: self.name.lower())

...

entity = SomeEntity(name='Nick')
entity.put()

这会存储具有以下属性值的实体:

assert entity.name == 'Nick'
assert entity.name_lower == 'nick'

如果我们将名称更改为“Nickie”,并请求 name_lower 的值,它会返回“nickie”:

entity.name = 'Nick'
assert entity.name_lower == 'nick'
entity.name = 'Nickie'
assert entity.name_lower == 'nickie'

Google Protocol RPC Message 属性

Google Protocol RPC 库为结构化数据使用 Message 对象,它们可以表示 RPC 请求、响应或其他内容。NDB 提供了一个 API,可让您将 Google Protocol RPC Message 对象存储为实体属性。假设您定义了一个 Message 子类:

from protorpc import messages
...
class Note(messages.Message):
    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

您可以使用 NDB 的 msgprop API,在 Datastore 上将 Note 对象存储为实体属性值。

from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
...
class NoteStore(ndb.Model):
    note = msgprop.MessageProperty(Note, indexed_fields=['when'])
    name = ndb.StringProperty()
...
my_note = Note(text='Excellent note', when=50)

ns = NoteStore(note=my_note, name='excellent')
key = ns.put()

new_notes = NoteStore.query(NoteStore.note.when >= 10).fetch()

如果您要查询字段名称,则字段名称必须编入索引。您可以使用 indexed_fields 参数为 MessageProperty 指定将编入索引的字段名称列表。

MessageProperty 支持许多(但不是全部)属性选项。它支持以下属性选项:

  • name
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

Message 属性并不支持 indexed 属性选项;您无法将 Message 值编入索引。(您可以按照上文所述将消息的字段编入索引。)

嵌套消息(使用 MessageField)也可行:

class Notebook(messages.Message):
    notes = messages.MessageField(Note, 1, repeated=True)
...
class SignedStorableNotebook(ndb.Model):
    author = ndb.StringProperty()
    nb = msgprop.MessageProperty(
        Notebook, indexed_fields=['notes.text', 'notes.when'])

MessageProperty 有一个特殊的属性选项 protocol,可指定消息对象是如何序列化到 Datastore 的。值是 protorpc.remote.Protocols 类使用的协议名称。支持的协议名称是 protobufprotojson,默认的协议名称是 protobuf

msgprop 还定义了 EnumProperty,这是一个可用于在实体中存储 protorpc.messages.Enum 值的属性类型。示例:

class Color(messages.Enum):
    RED = 620
    GREEN = 495
    BLUE = 450
...
class Part(ndb.Model):
    name = ndb.StringProperty()
    color = msgprop.EnumProperty(Color, required=True)
...
p1 = Part(name='foo', color=Color.RED)
print p1.color  # prints "RED"

EnumProperty 将值存储为整数;实际上,EnumPropertyIntegerProperty 的一个子类。这意味着您可以重命名枚举值,而无需修改已存储的实体,但不能为它们重新编号。

EnumProperty 支持以下属性选项

  • name
  • indexed
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

NDB 实体模型介绍

NDB 实体模型可以定义属性。实体属性有点像 Python 类的数据成员(保存数据的一种结构化方式);它们还有点像数据库架构中的字段。

典型应用通过定义继承自 Model 且具有某些属性 (property) 类特性 (attribute) 的类来定义数据模型。例如:


from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

此处,usernameuseridemail 都是 Account 的属性。

有多个其他属性类型。其中一些对表示日期和时间非常有用,并且具有方便快捷的自动更新功能。

应用可以指定属性上的选项来调整属性的行为,这些选项可以简化验证、设置默认值或更改查询索引。

模型可以有更复杂的属性。重复属性类似于列表。 结构化属性类似于对象。只读计算属性是通过函数定义的;这可以让您根据一个或多个其他属性轻松定义属性。 Expando 模型可以动态地定义属性。