实体属性参考

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

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

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

属性和值类型

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

支持以下值类型:

如果查询涉及的属性具有混合类型的值,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

用于验证且可能强制转换值的可选函数。

将使用参数(propvalue)调用,并且应返回(可能是强制的)值或引发异常。对强制性值再次调用该函数不应进一步修改该值。 (例如,返回 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 模块本身定义的基础类之间的继承关系)。

注意:App Engine 时钟时间始终以世界协调时间 (UTC) 表示。如果您将当前日期或时间 (datetime.datetime.now()) 用作一个值,或者在 datetime 对象和 POSIX 时间戳或时间元组之间转换,需要考虑这一点。不过,Datastore 中没有存储明确的时区信息,因此如果您非常谨慎,就可以使用这些属性来表示任何时区的当地时间,但前提是您使用当前时间或转换。

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

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

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

注意:当写入 auto_now_add=True 的属性的事务失败并稍后重试时,它将重复使用初次尝试的时间值,而不是将其更新为重试时间。如果事务永远不成功,该属性的值仍将设置在实体的内存副本中。

结构化属性

您可以构建模型的属性。 例如,您可以定义一个含有地址列表的模型类 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'}>

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

注意:具有嵌套属性(无论是否结构化)的 StructuredProperty 仅支持单层重复属性。StructuredProperty 可以重复,嵌套属性也可以重复,但两者不能同时重复。一种解决方法是使用不具有此限制的 LocalStructuredProperty(但不允许查询其属性值)。

计算属性

计算属性 (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'

注意:如果应用查询计算值,请使用 ComputedProperty。如果您只想使用 Python 代码的派生版本,请定义常规方法或使用 Python 的 @property 嵌入式。

注意:如果您在未手动指定键的情况下创建模型,而是依靠 Datastore 自动生成实体的 ID,那么当您第一次调用 put() 时,ComputedProperty 将无法读取 ID 字段,因为该字段是在 ID 生成之前计算的。如果您需要一个使用实体 ID 的 ComputedProperty,则可通过 allocate_ids 方法生成可用于创建实体的 ID 和键,这样您的 ComputedProperty 就可在实体的第一次 put() 调用中引用该 ID。

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 模型可以动态地定义属性。