元数据

注意强烈建议构建新应用的开发者使用 NDB 客户端库,它与 DB 客户端库相比具有多项优势,例如可通过 Memcache API 进行自动实体缓存。如果您当前使用的是较早的 DB 客户端库,请参阅 DB 到 NDB 的迁移指南

Datastore 能够以编程方式访问部分元数据以支持元编程,从而实现后端管理功能、简化一致性缓存以及实现类似的目标;例如,您可以使用该功能为应用构建自定义 Datastore 查看器。可用的元数据包括应用所用的实体组、命名空间、实体种类和属性的相关信息,以及每个属性的属性表示法

控制台中的 Datastore 信息中心还提供一些有关应用的元数据,但其中显示的数据在某些重要方面与这些函数返回的数据有所不同。

  • 时效性。信息中心内的数据每天只更新一次,而使用 API 读取元数据会获取当前数据。
  • 内容。信息中心内的某些元数据不能通过 API 获得;反之亦然。
  • 速度。元数据获取和查询的计费方式与 Datastore 获取和查询相同。元数据查询用于获取命名空间、种类和属性的相关信息,但执行起来通常较慢。根据经验,返回 N 个实体的元数据查询预计与各返回单个实体的 N 个普通查询的时间大致相同。此外,属性表示法查询(非仅限于键的属性查询)比仅限于键的属性查询速度要慢。对实体组元数据进行的元数据获取比获取常规实体要快一些。

辅助函数

以下函数可获取元数据信息:

  • get_entity_group_version() 可获取实体组的版本号;这对于了解自上次获取版本号以来实体组中是否有任何实体发生更改非常有用。
  • get_namespaces() 可返回一个列表,其中包含应用的所有命名空间(或指定范围内的命名空间)的名称。
  • get_kinds() 可返回一个列表,其中包含应用的所有实体种类(或指定范围内的实体种类)的名称。
  • get_properties_of_kind() 可返回一个列表,其中包含应用与给定实体种类关联的所有已编入索引的属性(或指定范围内已编入索引的属性)的名称。不包括未编入索引的属性。
  • get_representations_of_kind() 可返回一个字典,其中包含应用与给定实体种类关联的所有已编入索引的属性(或指定范围内已编入索引的属性)的表示法。该字典会将每个属性的名称映射到该属性的表示法列表:不包括未编入索引的属性。

实体组元数据

Cloud Datastore 提供实体组“版本”信息。版本必定是正数,且随实体组的每次更改而提高。

以下示例展示了如何获取实体组的版本:

from google.appengine.ext import db
from google.appengine.ext.db import metadata

class Simple(db.Model):
  x = db.IntegerProperty()

entity1 = Simple(x=11)
entity1.put()

# Print entity1's entity group version
print 'version', metadata.get_entity_group_version(entity1)

# Write to a different entity group
entity2 = Simple(x=22)
entity2.put()

# Will print the same version, as entity1's entity group has not changed
print 'version', metadata.get_entity_group_version(entity1)

# Change entity1's entity group by adding a new child entity
entity3 = Simple(x=33, parent=entity1.key())
entity3.put()

# Will print a higher version, as entity1's entity group has changed
print metadata.get_entity_group_version(entity1)

旧版行为

在旧版的实体组版本行为中,实体组版本仅在实体组发生更改时才会提高。旧版实体组元数据行为可用于多种用途,例如,可以始终如一地缓存对实体组的复杂祖先查询。

以下示例缓存查询结果(匹配结果的计数),并通过实体组版本的旧版行为来使用当前的缓存值:

from google.appengine.api import memcache
from google.appengine.ext import db
from google.appengine.ext.db import metadata

def count_entity_group(entity_group_key):
  """Count the entities in the specified entity group."""
  # Check if we have a cached version of the current entity group count
  cached = memcache.get(str(entity_group_key))
  if cached:
    (version, count) = cached
    # Is the cached value for the current version?
    if version == metadata.get_entity_group_version(entity_group_key):
      return count

  def tx():
    # Need to actually count entities. Using a transaction to get a consistent
    # count and entity group version.
    count = db.Query(keys_only=True).ancestor(entity_group_key).count(limit=5000)
    # Cache the count and the entity group version
    version = metadata.get_entity_group_version(entity_group_key)
    memcache.set(str(entity_group_key), (version, count))
    return count

  return db.run_in_transaction(tx)

对于从未写入的实体组,get_entity_group_version() 可能返回 None

对包含 __version__ 属性的特殊伪实体进行 get() 调用可获得实体组版本信息。如需了解详情,请参阅有关 EntityGroup 的参考文档。

元数据查询

如果前文所述的辅助函数不能满足您的需求,您可以使用显式元数据查询发出更复杂或更灵活的元数据请求。在 Python 中,此类查询的模型类均在 google.appengine.ext.db.metadata 软件包中进行定义。这些模型为元数据查询保留了一些特殊的实体种类:

模型类 实体种类
Namespace __namespace__
Kind __kind__
Property __property__

这些模型和种类不会与应用中可能已存在的其他同名模型和种类发生冲突。通过查询这些特殊种类,您可以检索包含所需元数据的实体。

元数据查询返回的实体是根据 Datastore 的当前状态动态生成的。虽然您可以创建 NamespaceKindProperty 模型类的本地实例,但任何将它们存储在 Datastore 中的尝试都将失败,并引发 BadRequestError 异常。

您可以使用属于以下两个类中任一类的查询对象来发出元数据查询:

  • 类方法 Namespace.all()Kind.all()Property.all()(继承自父类方法 Model.all())返回的 Query 对象
  • 用于 GQL 样式查询的 GqlQuery 对象

以下示例用于返回应用中所有实体种类的名称:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

for k in Kind.all():
  print "kind: '%s'" % k.kind_name

命名空间查询

如果您的应用使用 Namespaces API,则您可以使用命名空间查询来查找应用实体中所用的所有命名空间。这样您就可以跨多个命名空间执行管理功能等活动。

命名空间查询返回特殊种类 __namespace__ 的实体,其键名为命名空间的名称。(由空字符串 "" 指定的默认命名空间例外:由于空字符串不是有效的键名,因此此命名空间改为以数字 ID 1 为键。)此类查询支持通过特殊伪属性 __key__ 仅对范围进行过滤,该伪属性的值就是实体的键。结果可以按 __key__ 值升序(但不能降序)排序。由于 __namespace__ 实体没有属性,仅限于键的查询和非仅限于键的查询返回的信息相同。

命名空间实体是模型类 google.appengine.ext.db.metadata.Namespace 的实例。根据实体的键计算得出的字符串属性 namespace_name 会返回对应命名空间的名称。(如果键具有数字 ID 1,则该属性将返回空字符串。)为方便查询,Namespace 模型提供下列类方法:

例如,以下代码是辅助函数 get_namespaces() 的实现,该函数将返回一个列表,其中包含应用的所有命名空间(或范围在两个指定名称 startend 之间的命名空间)的名称:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Namespace

def get_namespaces(start=None, end=None):

  # Start with unrestricted namespace query
  q = Namespace.all()

  # Limit to specified range, if any
  if start is not None:
    q.filter('__key__ >=', Namespace.key_for_namespace(start))
  if end is not None:
    q.filter('__key__ <', Namespace.key_for_namespace(end))

  # Return list of query results
  return [ns.namespace_name for ns in q]

种类查询

种类查询返回种类为 __kind__ 的实体,其键名为实体种类的名称。此类型的查询隐式地限制于当前命名空间,并且仅支持对 __key__ 伪属性的范围进行过滤。结果可以按 __key__ 值升序(但不能降序)排序。由于 __kind__ 实体没有属性,仅限于键的查询和非仅限于键的查询返回的信息相同。

种类实体是模型类 google.appengine.ext.db.metadata.Kind 的实例。根据实体的键计算得出的字符串属性 kind_name 会返回对应实体种类的名称。为方便查询,Kind 模型提供下列类方法:

例如,以下代码是辅助函数 get_kinds() 的实现,该函数将返回一个列表,其中包含应用的所有实体种类(或范围在两个指定名称 startend 之间的实体种类)的名称:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

def get_kinds(start=None, end=None):

  # Start with unrestricted kind query
  q = Kind.all()

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Kind.key_for_kind(start))
  if end is not None:
    if end == '':
      return []        # Empty string is not a valid kind name, so can't filter
    q.filter('__key__ <', Kind.key_for_kind(end))

  # Return list of query results
  return [k.kind_name for k in q]

以下示例输出名称以小写字母开头的所有种类:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

# Start with unrestricted kind query
q = Kind.all()

# Limit to lowercase initial letters
q.filter('__key__ >=', Kind.key_for_kind('a'))
endChar = chr(ord('z') + 1)                        # Character after 'z'
q.filter('__key__ <', Kind.key_for_kind(endChar))

# Print query results
for k in q:
  print k.kind_name

属性查询

属性查询可返回种类为 __property__ 的实体,该实体表示与某实体种类关联的属性(无论这些属性目前是否在该种类的模型中定义)。代表种类 K 的属性 P 的实体按以下方式构建:

  • 对于该实体的键,种类为 __property__,键名为 p
  • 父实体的键具有种类 __kind__ 和键名 K

属性实体是模型类 google.appengine.ext.db.metadata.Property 的实例。根据实体的键计算得出的字符串属性 kind_nameproperty_name 会返回对应种类和属性的名称。Property 模型提供了四种类方法来简化 __property__ 键的构建和检查过程:

以下示例展示了这些方法:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

class Employee(db.Model):
  name = db.StringProperty()
  ssn = db.IntegerProperty()

employee_key = Property.key_for_kind("Employee")
employee_name_key = Property.key_for_property("Employee", "Name")

Property.key_to_kind(employee_key)           # Returns "Employee"
Property.key_to_property(employee_name_key)  # Returns "Name"

属性查询的行为取决于它是仅限于键还是非仅限于键(属性表示法)的查询,如下文的各个子部分中所述。

属性查询:仅限于键

仅限于键的属性查询会为指定实体种类的每个编入索引的属性返回一个键(不包括未编入索引的属性)。以下示例将输出应用所有实体种类的名称以及与各种类相关联的属性:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

# Create unrestricted keys-only property query
q = Property.all(keys_only=True)

# Print query results
for p in q:
  print "%s: %s" % (Property.key_to_kind(p), Property.key_to_property(p))

此类型的查询隐式地限制于当前命名空间,并且仅支持对伪属性 __key__ 的范围进行过滤,在这种情况下,键表示 __kind____property__ 实体。结果可以按 __key__ 值升序(但不能降序)排序。过滤适用于“种类属性对”,先按种类排序,再按属性排序:例如,假定您有一个具有以下属性的实体:

  • 种类 Account,其属性为:
    • balance
    • company
  • 种类 Employee,其属性为:
    • name
    • ssn
  • 种类 Invoice,其属性为:
    • date
    • amount
  • 种类 Manager,其属性为:
    • name
    • title
  • 种类 Product,其属性为:
    • description
    • price

返回属性数据的查询会如下所示:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

# Start with unrestricted keys-only property query
q = Property.all(keys_only=True)

# Limit range
q.filter('__key__ >=', Property.key_for_property("Employee", "salary"))
q.filter('__key__ <=', Property.key_for_property("Manager", "salary"))

# Print query results
for p in q:
  print "%s: %s" % (Property.key_to_kind(p), Property.key_to_property(p))

以上查询会返回以下结果:

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

请注意,结果既不包括种类 Employeename 属性和种类 Managertitle 属性,也不包括种类 AccountProduct 的任何属性,因为这些属性不在为查询指定的范围内。

属性查询还支持对 __kind____property__ 键进行祖先过滤,以将查询结果限为单一种类或属性。例如,您可以借助这一点获取与给定的实体种类关联的属性,如以下示例所示:

(辅助函数 get_properties_of_kind() 的实现)

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

def get_properties_of_kind(kind, start=None, end=None):

  # Start with unrestricted keys-only property query
  q = Property.all(keys_only=True)

  # Limit to specified kind
  q.ancestor(Property.key_for_kind(kind))

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Property.key_for_property(kind, start))
  if end is not None:
    if end == '':
      return []     # Empty string is not a valid property name, so can't filter
    q.filter('__key__ <', Property.key_for_property(kind, end))

  # Return list of query results
  return [Property.key_to_property(p) for p in q]

属性查询:非仅限于键(属性表示法)

非仅限于键的属性查询称为“属性表示法查询”,可返回各“种类属性对”所用表示法的相关额外信息(不包括未编入索引的属性)。为种类为 K 的属性 P 返回的实体具有的键与相应仅限于键的查询返回的键相同,同时具有返回属性表示法的额外 property_representation 属性。此属性的值是类 StringListProperty 的实例,对于在种类为 K 的任何实体中找到的属性 P 的每个表示法,这个类都包含一个对应的字符串。

请注意,表示法与属性类不同;多个属性类可以对应同一种表示法。(例如,StringPropertyPhoneNumberProperty 都使用 STRING 表示法。)

属性类与其表示法的对应关系如下表所示:

属性类 表示法
IntegerProperty INT64
FloatProperty DOUBLE
BooleanProperty BOOLEAN
StringProperty STRING
ByteStringProperty STRING
DateProperty INT64
TimeProperty INT64
DateTimeProperty INT64
GeoPtProperty POINT
PostalAddressProperty STRING
PhoneNumberProperty STRING
EmailProperty STRING
UserProperty USER
IMProperty STRING
LinkProperty STRING
CategoryProperty STRING
RatingProperty INT64
ReferenceProperty
SelfReferenceProperty
REFERENCE
blobstore.BlobReferenceProperty STRING
ListProperty 列表元素的表示法
StringListProperty 列表元素的表示法

例如,以下代码是辅助函数 get_representations_of_kind() 的实现,该函数将返回一个字典,其中包含应用与给定实体种类关联的所有已编入索引的属性(或范围在两个指定名称 startend 之间的已编入索引的属性)的表示法。该字典会将每个属性的名称映射到该属性的表示法列表:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

def get_representations_of_kind(kind, start=None, end=None):

  # Start with unrestricted non-keys-only property query
  q = Property.all()

  # Limit to specified kind
  q.ancestor(Property.key_for_kind(kind))

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Property.key_for_property(kind, start))
  if end is not None:
    if end == '':
      return []     # Empty string is not a valid property name, so can't filter
    q.filter('__key__ <', Property.key_for_property(kind, end))

  # Initialize result dictionary
  result = {}

  # Add query results to dictionary
  for p in q:
    result[p.property_name] = p.property_representation

  # Return dictionary
  return result