Metadados

Observação: é altamente recomendável a desenvolvedores que criam novos aplicativos usar a biblioteca de cliente NDB, porque ela oferece diversos benefícios em comparação com esta biblioteca de cliente, como armazenamento em cache automático de entidades por meio da API Memcache. Se você estiver usando a antiga biblioteca de cliente DB, leia o Guia de migração de DB para NDB.

O Datastore fornece acesso programático a alguns de seus metadados para oferecer suporte à metaprogramação, implementar funções administrativas de back-end, simplificar o armazenamento em cache consistente e finalidades semelhantes. É possível usá-lo, por exemplo, para criar um visualizador personalizado do Datastore para seu aplicativo. Nos metadados disponíveis, há informações sobre grupos de entidades, namespaces, tipos de entidade e propriedades utilizadas pelo aplicativo, além de representações de cada propriedade.

O Painel do Datastore, no console também fornece alguns metadados sobre seu aplicativo, mas os dados exibidos nele diferem, em alguns aspectos importantes, dos retornados por essas funções.

  • Atualização. A leitura de metadados pela API fornece dados atuais, enquanto os dados do painel são atualizados somente uma vez por dia.
  • Conteúdo. Alguns metadados no painel não estão disponíveis através das APIs e vice-versa.
  • Velocidade. Consultas e gets de metadados são faturados da mesma maneira que consultas e gets do Datastore. As consultas de metadados que buscam informações sobre namespaces, tipos e propriedades geralmente demoram a ser executadas. Como regra geral, uma consulta de metadados que retorna N entidades costuma levar mais ou menos o mesmo tempo que consultas N comuns, cada uma retornando uma única entidade. Além disso, as consultas de representação de propriedade, que são de propriedade sem chave, são mais lentas do que as consultas de propriedade apenas de chaves. Os resultados de metadados do grupo de entidades são um pouco mais rápidos do que uma entidade normal.

Funções auxiliares

As seguintes funções recebem informações de metadados:

  • get_entity_group_version() recebe um número de versão para um grupo de entidades. Isso é útil para descobrir se alguma entidade no grupo foi alterada desde a última vez que você recebeu o número da versão.
  • get_namespaces() retorna uma lista contendo os nomes de todos os namespaces de um aplicativo ou aqueles em um intervalo especificado.
  • get_kinds() retorna uma lista contendo os nomes de todos os tipos de entidade de um aplicativo ou aqueles em um intervalo especificado.
  • get_properties_of_kind() retorna uma lista contendo os nomes de todas as propriedades indexadas de um aplicativo (ou aquelas em um intervalo especificado) associadas a um determinado tipo de entidade. Propriedades não indexadas não estão incluídas.
  • get_representations_of_kind() retorna um dicionário que contém as representações de todas as propriedades indexadas de um aplicativo ou aquelas em um intervalo especificado associado a um determinado tipo de entidade. O dicionário mapeia o nome de cada propriedade a uma lista das representações dessa propriedade. Propriedades não indexadas não estão incluídas.

Metadados do grupo de entidades

No Cloud Datastore é possível ter acesso à "versão" de um grupo de entidades, um número estritamente positivo que tem garantia de aumentar a cada alteração no grupo de entidades.

O seguinte exemplo mostra como receber a versão de um grupo de entidades:

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)

Comportamento legado

No comportamento legado da versão do grupo de entidades, a versão aumenta apenas em alterações no grupo de entidades. O comportamento legado dos metadados do grupo de entidades poderia ser usado, por exemplo, para manter um cache consistente de uma consulta de ancestral complexa em um grupo de entidades.

Neste exemplo, armazenamos em cache os resultados da consulta (uma contagem de resultados correspondentes) e usamos o comportamento legado das versões do grupo de entidades para utilizar o valor em cache se ele for atual:

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() pode retornar None para um grupo de entidades que nunca foi gravado.

As versões de grupo de entidades são obtidas chamando get() em uma pseudo-entidade especial que contém uma propriedade __version__. Consulte a documentação de referência em EntityGroup para mais detalhes.

Consultas de metadados

Se as funções auxiliares descritas na seção anterior não atenderem às necessidades, você poderá emitir solicitações de metadados mais elaboradas ou flexíveis com uma consulta de metadados explícita. No Python, as classes de modelo para essas consultas são definidas no pacote google.appengine.ext.db.metadata. Esses modelos fornecem tipos de entidade especiais reservados para consultas de metadados:

Classe de modelo Tipo de entidade
Namespace __namespace__
Kind __kind__
Property __property__

Esses modelos e tipos não entrarão em conflito com outros de nomes iguais que talvez já existam no aplicativo. Ao consultar esses tipos especiais, você pode recuperar entidades que contenham os metadados desejados.

As entidades retornadas por consultas de metadados são geradas dinamicamente, com base no estado atual do Datastore. Embora seja possível criar instâncias locais das classes de modelo Namespace, Kind ou Property, qualquer tentativa de armazená-las no Datastore falhará com uma exceção BadRequestError.

É possível emitir consultas de metadados usando um objeto de consulta pertencente a uma de duas classes:

  • Um objeto Query retornado pelo método de classe Namespace.all(), Kind.all() ou Property.all() (herdado do método da superclasse Model.all())
  • Um objeto GqlQuery para consultas no estilo GQL

Veja no exemplo a seguir como retornar os nomes de todos os tipos de entidade em um aplicativo:

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

Consultas de namespace

Se o aplicativo opta pela API Namespaces, é possível utilizar uma consulta de namespace para encontrar todos os namespaces usados nas entidades do aplicativo. Isso permite que você execute atividades, como funções administrativas, em vários namespaces.

As consultas de namespace retornam entidades do tipo especial __namespace__, cujo nome de chave é o nome de um namespace. Uma exceção é o namespace padrão designado pela string vazia "": como a string vazia não é um nome de chave válido, esse namespace é identificado pelo ID numérico 1. As consultas desse tipo são compatíveis apenas com a filtragem de intervalos na pseudopropriedade especial __key__, cujo valor é a chave da entidade. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). Como as entidades __namespace__ não têm propriedades, as consultas apenas de chaves e sem chave retornam as mesmas informações.

As entidades de namespace são instâncias da classe de modelo google.appengine.ext.db.metadata.Namespace. A propriedade de string namespace_name, calculada a partir da chave da entidade, retorna o nome do namespace correspondente. Caso a chave tenha um ID numérico 1, a propriedade retornará a string vazia. Para facilitar a consulta, o modelo Namespace fornece os seguintes métodos de classe:

Como exemplo, veja a implementação da função auxiliar get_namespaces(), que retorna uma lista com os nomes de todos os namespace de um aplicativo (ou aqueles no intervalo entre dois nomes especificados, start e end):

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]

Consultas de tipo

Consultas de tipo retornam entidades do tipo __kind__, cujo nome de chave é o nome de um tipo de entidade. As consultas desse tipo são implicitamente restritas ao namespace atual e são compatíveis apenas com a filtragem para intervalos acima da pseudopropriedade __key__. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). Como as entidades __kind__ não têm propriedades, as consultas apenas de chave e sem chave retornam as mesmas informações.

As entidades de tipo são instâncias da classe de modelo google.appengine.ext.db.metadata.Kind. A propriedade de string kind_name, calculada a partir da chave da entidade, retorna o nome do tipo de entidade correspondente. Para facilitar a consulta, o modelo Kind fornece os seguintes métodos de classe:

Como exemplo, veja a implementação da função auxiliar get_kinds(), que retorna uma lista com os nomes de todos os tipos de entidade de um aplicativo (ou aqueles no intervalo entre dois nomes especificados, start e end):

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]

O exemplo a seguir imprime todos os tipos com nomes que começam com uma letra minúscula:

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

Consultas de propriedade

As consultas de propriedade retornam entidades do tipo __property__ que denotam as propriedades associadas a um tipo de entidade (se essas propriedades estão definidas ou não no modelo do tipo). A entidade que representa a propriedade P do tipo K é criada da seguinte forma:

  • A chave da entidade tem o tipo __property__ e o nome de chave P.
  • A chave da entidade pai tem o tipo __kind__ e o nome da chave K.

Entidades de propriedade são instâncias da classe de modelo google.appengine.ext.db.metadata.Property. As propriedades de string kind_name e property_name, calculadas a partir da chave da entidade, retornam os nomes do tipo e da propriedade correspondentes. O modelo Property fornece quatro métodos de classe para simplificar a criação e a análise de chaves __property__:

O exemplo a seguir ilustra esses métodos:

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"

O comportamento de uma consulta de propriedade depende do fato de ela ser uma consulta apenas de chaves ou apenas sem chave (representação de propriedade), conforme detalhado nas subseções a seguir.

Consultas de propriedade: somente com chaves

As consultas de propriedade somente com chaves retornam uma chave para cada propriedade indexada de um tipo de entidade especificado. As propriedades não indexadas não estão incluídas. O exemplo abaixo imprime os nomes de todos os tipos de entidade do aplicativo, bem como as propriedades associadas com cada um deles:

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))

As consultas desse tipo são restritas implicitamente ao namespace atual e à filtragem de suporte apenas para intervalos na pseudopropriedade __key__, em que as chaves denotam entidades __kind__ ou __property__. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). A filtragem é aplicada a pares de propriedade-tipo, classificados primeiro por tipo e depois por propriedade. Por exemplo, suponha que você tenha uma entidade com estas propriedades:

  • tipo Account com as propriedades
    • balance
    • company
  • tipo Employee com as propriedades
    • name
    • ssn
  • tipo Invoice com as propriedades
    • date
    • amount
  • tipo Manager com as propriedades
    • name
    • title
  • tipo Product com as propriedades
    • description
    • price

A consulta para retornar os dados da propriedade tem esta aparência:

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))

A consulta acima apresenta o seguinte resultado:

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

Os resultados não incluem a propriedade name do tipo Employee e a propriedade title do tipo Manager, nem quaisquer propriedades dos tipos Account e Product, porque estão fora do intervalo especificado para a consulta.

As consultas de propriedade também são compatíveis com a filtragem de ancestrais em uma chave __kind__ ou __property__ para limitar os resultados da consulta a um único tipo ou propriedade. Você pode usar isso, por exemplo, para ver as propriedades associadas a um determinado tipo de entidade, como no exemplo a seguir:

(uma implementação da função auxiliar 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]

Consultas de propriedade: somente sem chaves (representação de propriedade)

As consultas de propriedade somente sem chaves, conhecidas como consultas de representação de propriedade, retornam mais informações sobre as representações usadas por cada par de propriedade-tipo. As propriedades não indexadas não estão incluídas. A entidade retornada para a propriedade P do tipo K tem a mesma chave de uma consulta apenas de chaves, junto com uma propriedade property_representation adicional que retorna as representações da propriedade. O valor dessa propriedade é uma instância da classe StringListProperty que contém uma string para cada representação da propriedade P encontrada em qualquer entidade do tipo K.

Representações e classes de propriedades não são iguais. Várias classes de propriedades podem ser mapeadas para a mesma representação. Por exemplo, StringProperty e PhoneNumberProperty usam a representação STRING.

Veja na tabela a seguir o mapeamento das classes de propriedades para as respectivas representações:

Classe de propriedade Representação
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 Listar a representação do elemento
StringListProperty Listar a representação do elemento

Como exemplo, veja a implementação da função auxiliar get_representations_of_kind(), que retorna um dicionário com as representações de todas as propriedades indexadas de um aplicativo (ou aquelas no intervalo entre dois nomes especificados, start e end) associados a um determinado tipo de entidade. O dicionário mapeia o nome de cada propriedade e cria uma lista das representações dessa propriedade.

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