メタデータ

注: 新しいアプリケーションを作成する際は、NDB クライアント ライブラリを使用することを強くおすすめします。NDB クライアント ライブラリには、Memcache API によるエンティティの自動キャッシュをはじめ、このクライアント ライブラリにはないメリットがあります。古い DB クライアント ライブラリを現在使用している場合は、DB から NDB への移行ガイドをお読みください。

Datastore では一部のメタデータにプログラムからアクセスできます。これによりメタプログラミングやバックエンドの管理機能の実装が可能となり、一貫性のあるキャッシュ保存が簡単になります。これは、アプリケーション用にカスタム Datastore ビューアを作成する場合などに役立ちます。使用できるメタデータとしては、アプリケーションが使用するエンティティ グループ、名前空間、エンティティの種類、プロパティに関する情報のほか、各プロパティのプロパティ表現などが挙げられます。

アプリケーションに関する一部のメタデータは、Cloud Console の Datastore ダッシュボードでも提供されています。ただし、ここに表示されるデータは、いくつかの重要な側面において関数から返されるデータとは異なります。

  • 鮮度: API を使用してメタデータを読み取ると最新のデータが取得されますが、ダッシュボードのデータが更新されるのは 1 日 1 回だけです。
  • 内容: ダッシュボードの一部のメタデータは 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 例外が発生して失敗します。

メタデータ クエリは、次の 2 つのクラスのいずれかに属するクエリ オブジェクトを使用して発行できます。

  • クラスメソッド Namespace.all()Kind.all()Property.all() によって返される Query オブジェクト(スーパークラス メソッド Model.all() から継承)
  • 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__ エンティティはプロパティを持たないため、キーのみのクエリでもキーのみではないクエリでも同じ情報が返されます。

Namespace のエンティティは、モデルクラス google.appengine.ext.db.metadata.Namespace のインスタンスです。文字列プロパティ namespace_name(エンティティのキーから作成されます)は、対応するエンティティの種類の名前を返します(キーが数値 ID 1 の場合、このプロパティは空の文字列を返します)。クエリを容易に実行できるように、Namespace モデルは次のクラスメソッドを備えています。

例として、ヘルパー関数 get_namespaces() の実装を次に示します。これは、アプリケーションのすべての名前空間(または指定された 2 つの名前、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__ エンティティはプロパティを持たないため、キーのみのクエリでもキーのみではないクエリでも同じ情報が返されます。

Kind のエンティティは、モデルクラス google.appengine.ext.db.metadata.Kind のインスタンスです。文字列プロパティ kind_name(エンティティのキーから作成されます)は、対応するエンティティの種類の名前を返します。クエリを容易に実行できるように、Kind モデルは次のクラスメソッドを備えています。

以下にヘルパー関数 get_kinds() の実装例を示します。この関数は、アプリケーションのすべてのエンティティの種類(または指定された 2 つの名前、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__ であるエンティティを返します。これは、エンティティの種類に関連付けられたプロパティを示します(そのようなプロパティが現在その種類のモデルで定義されているかどうかは問いません)。プロパティが P、種類が K で表現されるエンティティは次のように構築されます。

  • エンティティのキーの種類は __property__ で、キー名は P です。
  • 親エンティティのキーの種類は __kind__ で、キー名は K です。

Property のエンティティは、モデルクラス google.appengine.ext.db.metadata.Property のインスタンスです。文字列プロパティ kind_nameproperty_name(エンティティのキーから作成されます)は、対応する種類とプロパティの名前を返します。Property モデルは、__property__ キーを容易に作成して確認できるように、次の 4 つのクラスメソッドを備えています。

  • Property.key_for_kind() は、指定したエンティティの種類の __property__ キーに対して親の __kind__ キーを作成します。
  • Property.key_for_property() は、指定した種類とプロパティの __property__ キーを作成します。
  • Property.key_to_kind() は、__property__ キーに関連付けられている種類の名前を返します。
  • Property.key_to_property() は、__property__ キーに関連付けられているプロパティ名を返します(キーが種類のみを指定している場合は None を返します)。

以下にこれらのメソッドの使用例を示します。

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 プロパティは含まれません。また、種類 Account と種類 Product のプロパティはすべて含まれません。これらのプロパティは、クエリに指定された範囲に含まれないためです。

プロパティ クエリは __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 の表現ごとに 1 つの文字列が含まれます。

表現はプロパティ クラスとは異なり、複数のプロパティ クラスが同じ表現にマップされる場合があります(たとえば、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() の実装例を示します。この関数は、特定のエンティティの種類に関連付けられたアプリケーションのすべてのインデックス付きプロパティ(または指定された 2 つの名前、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