Metadados

Nota: os programadores que criam novas aplicações são fortemente aconselhados a usar a biblioteca de cliente NDB, que tem várias vantagens em comparação com esta biblioteca de cliente, como o armazenamento em cache automático de entidades através da API Memcache. Se estiver a usar atualmente a biblioteca cliente DB mais antiga, leia o guia de migração de DB para NDB

O Datastore oferece acesso programático a alguns dos respetivos metadados para suportar a metaprogramação, implementar funções administrativas de back-end, simplificar o armazenamento em cache consistente e fins semelhantes. Pode usá-lo, por exemplo, para criar um visualizador do Datastore personalizado para a sua aplicação. Os metadados disponíveis incluem informações sobre os grupos de entidades, os espaços de nomes, os tipos de entidades e as propriedades que a sua aplicação usa, bem como as representações de propriedades para cada propriedade.

O painel de controlo do Datastore na consola também fornece alguns metadados sobre a sua aplicação, mas os dados apresentados aí diferem em alguns aspetos importantes dos devolvidos por estas funções. Google Cloud

  • Atualidade. A leitura de metadados através da API obtém dados atuais, enquanto os dados no painel de controlo são atualizados apenas uma vez por dia.
  • Conteúdo. Alguns metadados no painel de controlo não estão disponíveis através das APIs. O inverso também é verdadeiro.
  • Velocidade. As consultas e as obtenções de metadados são faturadas da mesma forma que as consultas e as obtenções do Datastore. As consultas de metadados que obtêm informações sobre espaços de nomes, tipos e propriedades são, geralmente, lentas de executar. Como regra geral, espere que uma consulta de metadados que devolve N entidades demore aproximadamente o mesmo tempo que N consultas comuns, cada uma das quais devolve uma única entidade. Além disso, as consultas de representação de propriedades (consultas de propriedades apenas com chaves) são mais lentas do que as consultas de propriedades apenas com chaves. As obtenções de metadados de metadados de grupos de entidades são ligeiramente mais rápidas do que a obtenção de uma entidade normal.

Funções auxiliares

As seguintes funções obtêm informações de metadados:

  • get_entity_group_version() recebe um número de versão para um grupo de entidades. Isto é útil para saber se alguma entidade no grupo foi alterada desde a última vez que recebeu o número de versão.
  • get_namespaces() devolve uma lista com os nomes de todos os espaços de nomes de uma aplicação ou os de um intervalo especificado.
  • get_kinds() devolve uma lista com os nomes de todos os tipos de entidades de uma aplicação ou os de um intervalo especificado.
  • get_properties_of_kind() devolve uma lista com os nomes de todas as propriedades indexadas de uma aplicação (ou as que se encontram num intervalo especificado) associadas a um determinado tipo de entidade. As propriedades não indexadas não estão incluídas.
  • get_representations_of_kind() devolve um dicionário que contém as representações de todas as propriedades indexadas de uma aplicação ou as que se encontram num intervalo especificado associado a um determinado tipo de entidade. O dicionário mapeia o nome de cada propriedade para uma lista das representações dessa propriedade. As propriedades não indexadas não estão incluídas.

Metadados do grupo de entidades

O Cloud Datastore fornece acesso à "versão" de um grupo de entidades, um número estritamente positivo que tem a garantia de aumentar em todas as alterações ao grupo de entidades.

O exemplo seguinte mostra como obter 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 antigo

No comportamento da versão do grupo de entidades antigo, a versão do grupo de entidades só aumenta quando são feitas alterações ao grupo de entidades. O comportamento dos metadados do grupo de entidades antigo pode ser usado, por exemplo, para manter uma cache consistente de uma consulta de antepassados complexa num grupo de entidades.

Este exemplo coloca em cache os resultados da consulta (uma contagem de resultados correspondentes) e usa o comportamento antigo das versões do grupo de entidades para usar o valor em cache se estiver atualizado:

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 devolver None para um grupo de entidades que nunca foi escrito.

As versões do grupo de entidades são obtidas chamando get() numa pseudoentidade especial que contém uma propriedade __version__. Consulte a documentação de referência sobre EntityGroup para ver detalhes.

Consultas de metadados

Se as funções auxiliares descritas na secção anterior não satisfizerem as suas necessidades, pode emitir pedidos de metadados mais elaborados ou flexíveis com uma consulta de metadados explícita. Em Python, as classes de modelos para essas consultas estão definidas no pacote google.appengine.ext.db.metadata. Estes modelos fornecem tipos de entidades especiais que estão reservados para consultas de metadados:

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

Estes modelos e tipos não entram em conflito com outros do mesmo nome que já possam existir na sua aplicação. Ao consultar estes tipos especiais, pode obter entidades que contêm os metadados pretendidos.

As entidades devolvidas pelas consultas de metadados são geradas dinamicamente com base no estado atual do Datastore. Embora possa criar instâncias locais das classes de modelos Namespace, Kind ou Property, qualquer tentativa de as armazenar no Datastore falha com uma exceção BadRequestError.

Pode emitir consultas de metadados através de um objeto de consulta pertencente a uma de duas classes:

  • Um objeto Query devolvido 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

O exemplo seguinte devolve os nomes de todos os tipos de entidades numa aplicação:

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 espaços de nomes

Se a sua aplicação usar a API Namespaces, pode usar uma consulta de espaço de nomes para encontrar todos os espaços de nomes usados nas entidades da aplicação. Isto permite-lhe realizar atividades como funções administrativas em vários espaços de nomes.

As consultas de espaço de nomes devolvem entidades do tipo especial __namespace__ cuja chave é o nome de um espaço de nomes. (Uma exceção é o espaço de nomes predefinido designado pela string vazia "": uma vez que a string vazia não é um nome de chave válido, este espaço de nomes é indexado com o ID numérico 1.) As consultas deste tipo suportam a filtragem apenas para intervalos sobre a pseudopropriedade especial __key__, cujo valor é a chave da entidade. Os resultados podem ser ordenados pelo valor __key__ ascendente (mas não descendente). Uma vez que as entidades __namespace__ não têm propriedades, as consultas apenas com chaves e sem chaves devolvem as mesmas informações.

As entidades de espaço de nomes 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, devolve o nome do espaço de nomes correspondente. (Se a chave tiver o ID numérico 1, a propriedade devolve a string vazia.) Para facilitar as consultas, o modelo Namespace fornece os seguintes métodos de classe:

Por exemplo, aqui está a implementação da função auxiliar get_namespaces(), que devolve uma lista com os nomes de todos os espaços de nomes de uma aplicação (ou os que estão 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

As consultas de tipo devolvem entidades do tipo __kind__ cujo nome da chave é o nome de um tipo de entidade. As consultas deste tipo estão implicitamente restritas ao espaço de nomes atual e suportam a filtragem apenas para intervalos sobre a pseudopropriedade __key__. Os resultados podem ser ordenados por valor de __key__ ascendente (mas não descendente). Uma vez que as entidades __kind__ não têm propriedades, as consultas apenas com chaves e sem chaves devolvem as mesmas informações.

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

Por exemplo, aqui está a implementação da função auxiliar get_kinds(), que devolve uma lista com os nomes de todos os tipos de entidades de uma aplicação (ou os que estão 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 seguinte imprime todos os tipos cujos nomes começam por 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 propriedades

As consultas de propriedades devolvem entidades do tipo __property__ que denotam as propriedades associadas a um tipo de entidade (independentemente de essas propriedades estarem ou não definidas atualmente 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 da chave P.
  • A chave da entidade principal tem o tipo __kind__ e o nome da chave K.

As entidades de propriedades 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, devolvem os nomes do tipo e da propriedade correspondentes. O modelo Property oferece quatro métodos de classe para simplificar a criação e a análise de chaves __property__:

O exemplo seguinte ilustra estes 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 de ser uma consulta apenas com chaves ou sem ser apenas com chaves (representação de propriedade), conforme detalhado nas subsecções abaixo.

Consultas de propriedades: apenas chaves

As consultas de propriedades apenas com chaves devolvem 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 seguinte imprime os nomes de todos os tipos de entidades de uma aplicação e as propriedades associadas a cada um:

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 deste tipo estão implicitamente restritas ao espaço de nomes atual e suportam a filtragem apenas para intervalos sobre a pseudopropriedade __key__, em que as chaves denotam entidades __kind__ ou __property__. Os resultados podem ser ordenados pelo valor __key__ ascendente (mas não descendente). A filtragem é aplicada a pares de tipo-propriedade, ordenados primeiro por tipo e, em segundo lugar, por propriedade. Por exemplo, suponhamos que tem uma entidade com estas propriedades:

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

A consulta para devolver os dados da propriedade teria o seguinte aspeto:

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 devolveria o seguinte:

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

Repare que 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 propriedades também suportam a filtragem de antepassados numa chave __kind__ ou __property__, para limitar os resultados da consulta a um único tipo ou propriedade. Pode usar isto, por exemplo, para obter as propriedades associadas a um determinado tipo de entidade, como no exemplo seguinte:

(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 propriedades: não apenas chaves (representação de propriedades)

As consultas de propriedades que não são apenas de chaves, conhecidas como consultas de representação de propriedades, devolvem informações adicionais sobre as representações usadas por cada par de tipo-propriedade. (As propriedades não indexadas não estão incluídas.) A entidade devolvida para a propriedade P do tipo K tem a mesma chave que para uma consulta apenas de chaves, juntamente com uma propriedade property_representation adicional que devolve as representações da propriedade. O valor desta 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.

Tenha em atenção que as representações não são o mesmo que as classes de propriedades. Várias classes de propriedades podem ser mapeadas para a mesma representação. (Por exemplo, StringProperty e PhoneNumberProperty usam a representação STRING.)

A tabela seguinte faz o mapeamento das classes de propriedades para as respetivas 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 Representação do elemento de lista
StringListProperty Representação do elemento de lista

Por exemplo, aqui está a implementação da função auxiliar get_representations_of_kind(), que devolve um dicionário que contém as representações de todas as propriedades indexadas de uma aplicação (ou as que estão no intervalo entre dois nomes especificados, start e end) associadas a um determinado tipo de entidade. O dicionário mapeia o nome de cada propriedade para 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