Metadatos

Nota: Se recomienda encarecidamente a los desarrolladores que creen aplicaciones nuevas que usen la biblioteca de cliente de NDB, que ofrece varias ventajas en comparación con esta biblioteca de cliente, como el almacenamiento automático en caché de entidades mediante la API Memcache. Si actualmente usas la biblioteca de cliente de DB anterior, consulta la guía de migración de DB a NDB.

Datastore proporciona acceso mediante programación a algunos de sus metadatos para admitir la metaprogramación, implementar funciones administrativas de backend, simplificar el almacenamiento en caché coherente y otros fines similares. Por ejemplo, puedes usarlo para crear un visor de Datastore personalizado para tu aplicación. Los metadatos disponibles incluyen información sobre los grupos de entidades, los espacios de nombres, los tipos de entidades y las propiedades que usa tu aplicación, así como las representaciones de propiedades de cada propiedad.

El panel de control de Datastore de la consola Google Cloud también proporciona algunos metadatos sobre tu aplicación, pero los datos que se muestran allí difieren en algunos aspectos importantes de los que devuelven estas funciones.

  • Actualización. Al leer los metadatos mediante la API, se obtienen los datos actuales, mientras que los datos del panel de control solo se actualizan una vez al día.
  • Contenido. Algunos metadatos del panel de control no están disponibles a través de las APIs, y viceversa.
  • Velocidad. Las operaciones de obtención y consulta de metadatos se facturan de la misma forma que las operaciones de obtención y consulta de Datastore. Las consultas de metadatos que obtienen información sobre espacios de nombres, tipos y propiedades suelen tardar en ejecutarse. Por lo general, una consulta de metadatos que devuelva N entidades tardará aproximadamente lo mismo que N consultas normales que devuelvan una sola entidad. Además, las consultas de representación de propiedades (consultas de propiedades que no son solo de claves) son más lentas que las consultas de propiedades que son solo de claves. Las obtenciones de metadatos de grupos de entidades son algo más rápidas que las de entidades normales.

Funciones auxiliares

Las siguientes funciones obtienen información de metadatos:

  • get_entity_group_version() Obtiene un número de versión de un grupo de entidades. Esto es útil para saber si alguna entidad del grupo ha cambiado desde la última vez que obtuviste el número de versión.
  • get_namespaces() devuelve una lista que contiene los nombres de todos los espacios de nombres de una aplicación o los de un intervalo especificado.
  • get_kinds() devuelve una lista que contiene los nombres de todos los tipos de entidades de una aplicación o los que se encuentran en un intervalo especificado.
  • get_properties_of_kind() devuelve una lista que contiene los nombres de todas las propiedades indexadas de una aplicación (o las que se encuentran en un intervalo especificado) asociadas a un tipo de entidad determinado. No se incluyen las propiedades sin indexar.
  • get_representations_of_kind() devuelve un diccionario que contiene las representaciones de todas las propiedades indexadas de una aplicación o las que se encuentran en un intervalo especificado asociado a un tipo de entidad determinado. El diccionario asigna el nombre de cada propiedad a una lista de representaciones de esa propiedad. No se incluyen las propiedades sin indexar.

Metadatos de grupos de entidades

Cloud Datastore proporciona acceso a la "versión" de un grupo de entidades, un número estrictamente positivo que aumenta en cada cambio del grupo de entidades.

En el siguiente ejemplo se muestra cómo obtener la versión de un 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)

Comportamiento antiguo

En el comportamiento de la versión antigua del grupo de entidades, la versión del grupo de entidades solo aumenta cuando se producen cambios en el grupo de entidades. El comportamiento de los metadatos de grupos de entidades antiguos se puede usar, por ejemplo, para mantener una caché coherente de una consulta de ancestros compleja en un grupo de entidades.

En este ejemplo se almacenan en caché los resultados de una consulta (un recuento de los resultados coincidentes) y se usa el comportamiento antiguo de las versiones de grupos de entidades para usar el valor almacenado en caché si está actualizado:

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() puede devolver None para un grupo de entidades que nunca se haya escrito.

Las versiones de grupos de entidades se obtienen llamando a get() en una pseudoentidad especial que contiene una propiedad __version__. Consulta la documentación de referencia sobre EntityGroup para obtener más información.

consultas de metadatos

Si las funciones auxiliares descritas en la sección anterior no satisfacen tus necesidades, puedes enviar solicitudes de metadatos más elaboradas o flexibles con una consulta de metadatos explícita. En Python, las clases de modelo de estas consultas se definen en el paquete google.appengine.ext.db.metadata. Estos modelos proporcionan tipos de entidades especiales que se reservan para las consultas de metadatos:

Clase de modelo Tipo de entidad
Namespace __namespace__
Kind __kind__
Property __property__

Estos modelos y tipos no entrarán en conflicto con otros que tengan el mismo nombre y que ya puedan existir en tu aplicación. Si consultas estos tipos especiales, puedes recuperar las entidades que contengan los metadatos que quieras.

Las entidades devueltas por las consultas de metadatos se generan de forma dinámica en función del estado actual de Datastore. Aunque puedes crear instancias locales de las clases de modelo Namespace, Kind o Property, cualquier intento de almacenarlas en Datastore fallará y se producirá una excepción BadRequestError.

Puede enviar consultas de metadatos mediante un objeto de consulta que pertenezca a una de estas dos clases:

  • Un objeto Query devuelto por el método de clase Namespace.all(), Kind.all() o Property.all() (heredado del método de superclase Model.all())
  • Objeto GqlQuery para consultas de estilo GQL

En el siguiente ejemplo se devuelven los nombres de todos los tipos de entidad de una aplicación:

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 espacio de nombres

Si tu aplicación usa la API Namespaces, puedes usar una consulta de espacio de nombres para encontrar todos los espacios de nombres que se usan en las entidades de la aplicación. Esto te permite realizar actividades como funciones administrativas en varios espacios de nombres.

Las consultas de espacios de nombres devuelven entidades del tipo especial __namespace__ cuya clave name es el nombre de un espacio de nombres. (La excepción es el espacio de nombres predeterminado, designado por la cadena vacía "". Como la cadena vacía no es un nombre de clave válido, este espacio de nombres se identifica con el ID numérico 1). Las consultas de este tipo solo admiten filtros de intervalos de la pseudopropiedad especial __key__, cuyo valor es la clave de la entidad. Los resultados se pueden ordenar por el valor __key__ en orden ascendente (pero no descendente). Como las entidades __namespace__ no tienen propiedades, tanto las consultas de solo claves como las que no son de solo claves devuelven la misma información.

Las entidades de espacio de nombres son instancias de la clase de modelo google.appengine.ext.db.metadata.Namespace. La propiedad de cadena namespace_name, calculada a partir de la clave de la entidad, devuelve el nombre del espacio de nombres correspondiente. Si la clave tiene el ID numérico 1, la propiedad devuelve la cadena vacía. Para facilitar las consultas, el modelo Namespace proporciona los siguientes métodos de clase:

Por ejemplo, aquí tienes la implementación de la función auxiliar get_namespaces(), que devuelve una lista que contiene los nombres de todos los espacios de nombres de una aplicación (o los que se encuentran en el intervalo entre dos nombres especificados, start y 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

Las consultas de tipo devuelven entidades de tipo __kind__ cuya clave es el nombre de un tipo de entidad. Las consultas de este tipo se restringen implícitamente al espacio de nombres actual y solo admiten filtros de intervalos de la pseudopropiedad __key__. Los resultados se pueden ordenar de forma ascendente (pero no descendente) por el valor __key__. Como las entidades __kind__ no tienen propiedades, las consultas que solo devuelven claves y las que no devuelven la misma información.

Las entidades de tipo son instancias de la clase de modelo google.appengine.ext.db.metadata.Kind. La propiedad de cadena kind_name, calculada a partir de la clave de la entidad, devuelve el nombre del tipo de entidad correspondiente. Para facilitar las consultas, el modelo Kind proporciona los siguientes métodos de clase:

Por ejemplo, aquí se muestra la implementación de la función auxiliar get_kinds(), que devuelve una lista que contiene los nombres de todos los tipos de entidades de una aplicación (o los que se encuentran en el intervalo entre dos nombres especificados, start y 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]

En el siguiente ejemplo se imprimen todos los tipos cuyos nombres empiezan por una 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 propiedades

Las consultas de propiedades devuelven entidades de tipo __property__ que denotan las propiedades asociadas a un tipo de entidad (tanto si esas propiedades están definidas en el modelo del tipo como si no). La entidad que representa la propiedad P de tipo K se crea de la siguiente manera:

  • La clave de la entidad tiene el tipo __property__ y el nombre de clave P.
  • La clave de la entidad superior tiene el tipo __kind__ y el nombre de clave K.

Las entidades de propiedad son instancias de la clase de modelo google.appengine.ext.db.metadata.Property. Las propiedades de cadena kind_name y property_name, calculadas a partir de la clave de la entidad, devuelven los nombres del tipo y la propiedad correspondientes. El modelo Property proporciona cuatro métodos de clase para simplificar la creación y el examen de claves __property__:

En el siguiente ejemplo se muestran estos 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"

El comportamiento de una consulta de propiedad depende de si es una consulta solo de claves o una consulta que no es solo de claves (representación de propiedad), como se detalla en las subsecciones siguientes.

Consultas de propiedades: solo claves

Las consultas de propiedades solo con claves devuelven una clave por cada propiedad indexada de un tipo de entidad especificado. Las propiedades sin indexar no se incluyen. En el siguiente ejemplo se imprimen los nombres de todos los tipos de entidades de una aplicación y las propiedades asociadas a cada uno:

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

Las consultas de este tipo se restringen implícitamente al espacio de nombres actual y solo admiten filtros de intervalos de la pseudopropiedad __key__, donde las claves denotan entidades __kind__ o __property__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) __key__. El filtrado se aplica a los pares de tipo y propiedad, ordenados primero por tipo y, después, por propiedad. Por ejemplo, supongamos que tiene una entidad con estas propiedades:

  • tipo Account con propiedades
    • balance
    • company
  • tipo Employee con propiedades
    • name
    • ssn
  • tipo Invoice con propiedades
    • date
    • amount
  • tipo Manager con propiedades
    • name
    • title
  • tipo Product con propiedades
    • description
    • price

La consulta para devolver los datos de la propiedad sería la siguiente:

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

La consulta anterior devolvería lo siguiente:

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

Ten en cuenta que los resultados no incluyen la propiedad name del tipo Employee ni la propiedad title del tipo Manager, ni ninguna propiedad de los tipos Account y Product, porque están fuera del intervalo especificado en la consulta.

Las consultas de propiedades también admiten el filtrado de ancestros en una clave __kind__ o __property__ para limitar los resultados de la consulta a un solo tipo o propiedad. Por ejemplo, puedes usarlo para obtener las propiedades asociadas a un tipo de entidad determinado, como en el siguiente ejemplo:

(una implementación de la función 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 propiedades: no solo de claves (representación de la propiedad)

Las consultas de propiedades que no son solo de claves, conocidas como consultas de representación de propiedades, devuelven información adicional sobre las representaciones utilizadas por cada par de tipo y propiedad. Las propiedades sin indexar no se incluyen. La entidad devuelta para la propiedad P de tipo K tiene la misma clave que la de una consulta de solo claves correspondiente, así como una propiedad property_representation adicional que devuelve las representaciones de la propiedad. El valor de esta propiedad es una instancia de la clase StringListProperty que contiene una cadena por cada representación de la propiedad P que se encuentra en cualquier entidad de tipo K.

Ten en cuenta que las representaciones no son lo mismo que las clases de propiedad. Varias clases de propiedad pueden asignarse a la misma representación. Por ejemplo, StringProperty y PhoneNumberProperty usan la representación STRING.

En la siguiente tabla se muestra la asignación de clases de propiedad a sus representaciones:

Clase Property Representación
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 Representación del elemento de lista
StringListProperty Representación del elemento de lista

Por ejemplo, aquí se muestra la implementación de la función auxiliar get_representations_of_kind(), que devuelve un diccionario que contiene las representaciones de todas las propiedades indexadas de una aplicación (o las que se encuentran en el intervalo entre dos nombres especificados, start y end) asociadas a un tipo de entidad determinado. El diccionario asigna el nombre de cada propiedad a una lista de representaciones de esa propiedad:

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