Metadata

Nota: Se recomienda enfáticamente a los desarrolladores que compilan aplicaciones nuevas que usen la biblioteca cliente de NDB, ya que tiene muchas ventajas en comparación con esta biblioteca cliente, como el almacenamiento en caché automático de entidades mediante la API de Memcache. Si por el momento usas la biblioteca cliente de DB anterior, lee la Guía de migración de DB a NDB.

Datastore proporciona acceso programático a algunos de los metadatos a fin de admitir la metaprogramación, implementar funciones administrativas de backend, simplificar el almacenamiento en caché coherente y propósitos similares. Puedes usarlo, por ejemplo, si deseas compilar un visualizador de Datastore personalizado para la aplicación. Los metadatos disponibles incluyen información sobre los grupos de entidades, los espacios de nombres, los tipos de entidad y las propiedades que usa tu aplicación, así como las representaciones de propiedad para cada propiedad.

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

  • Actualidad: Cuando se leen los metadatos con la API, se obtienen los datos actuales, mientras que los datos del panel se actualizan solo una vez por día.
  • Contenido: Algunos metadatos del panel no están disponibles mediante las API y viceversa.
  • Velocidad: Las consultas y las solicitudes GET de metadatos se facturan de la misma forma que las de Datastore. En general, las consultas de metadatos que recuperan información sobre espacios de nombres, tipos y propiedades son de ejecución lenta. Como regla general, puedes esperar que una consulta de metadatos que muestra N entidades demore el mismo tiempo que N consultas ordinarias que muestran una sola entidad cada una. Además, las consultas de representación de propiedad (consultas de propiedad que no son de solo claves) son más lentas que las consultas de propiedad de solo claves. Las operaciones get de metadatos de grupos de entidades son algo más rápidas que las de una entidad normal.

Funciones auxiliares

Las siguientes funciones obtienen información de metadatos:

  • get_entity_group_version() obtiene un número de versión para un grupo de entidades. Esto es útil a fin de averiguar si alguna entidad del grupo cambió desde la última vez que obtuviste el número de versión.
  • get_namespaces() muestra una lista que contiene los nombres de todos los espacios de nombres de una aplicación o aquellos en un rango especificado.
  • get_kinds() muestra una lista que contiene los nombres de todas las categorías de entidades de una aplicación o aquellos en un rango especificado.
  • get_properties_of_kind() muestra una lista que contiene los nombres de todas las propiedades indexadas de una aplicación (o las de un rango especificado) asociadas con una categoría de entidad determinada. No se incluyen las propiedades que no están indexadas.
  • get_representations_of_kind() muestra un diccionario que contiene las representaciones de todas las propiedades indexadas de una aplicación o que se encuentran en un rango especificado asociado con una categoría de entidad determinada. El diccionario asigna el nombre de cada propiedad a una lista de las representaciones de esa propiedad. No se incluyen las propiedades que no están indexadas.

Metadatos de grupo de entidades

Cloud Datastore brinda acceso a la “versión” de un grupo de entidades, un número estrictamente positivo que aumenta con cada cambio que se realice en el grupo de entidades.

El siguiente ejemplo 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 heredado

Según el comportamiento heredado de la versión del grupo de entidades, esa versión aumenta solo cuando se incorporan cambios en el grupo de entidades. El comportamiento heredado de los metadatos del grupo de entidades se podría usar, por ejemplo, para mantener una caché coherente de una consulta principal compleja en un grupo de entidades.

En este ejemplo, se almacenan en caché los resultados de consultas (un recuento de resultados coincidentes) y se usa el comportamiento heredado de las versiones de grupos de entidades para emplear el valor almacenado en caché si es actual:

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 mostrar el valor None para un grupo de entidades en el que nunca realizaron operaciones de escritura.

Las versiones del grupo de entidades se obtienen mediante una llamada a get() en una seudoentidad especial que contiene una propiedad __version__. Consulta la documentación de referencia sobre EntityGroup para obtener más detalles.

Consultas de metadatos

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

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

Estos modelos y tipos no entrarán en conflicto con otros del mismo nombre que puedan existir en tu aplicación. Si consultas estas categorías especiales, puedes recuperar entidades que contengan los metadatos deseados.

Las entidades que muestran las consultas de metadatos se generan de forma dinámica, según el estado actual de Datastore. Si bien puedes crear instancias locales de las clases de modelo Namespace, Kind o Property, cualquier intento de almacenarlas en Datastore fallará con una excepción BadRequestError.

Puedes emitir consultas de metadatos mediante un objeto de consulta que pertenezca a una de estas dos clases:

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

En el siguiente ejemplo, se muestran los nombres de todas las categorías de entidades 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 la aplicación usa la API de espacios de nombres, puedes usar una consulta de espacio de nombres para encontrar todos los 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 espacio de nombres muestran entidades de la categoría especial __namespace__ cuyo nombre de clave es el nombre de un espacio de nombres. Una excepción a esto es el espacio de nombres predeterminado que designa la string vacía ""; debido a que la string vacía no es un nombre de clave válido, este espacio de nombres recibe una clave con el ID numérico 1 en su lugar. Las consultas de este tipo solo admiten filtros para rangos superiores a la seudopropiedad especial __key__, cuyo valor es la clave de la entidad. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Debido a que las entidades __namespace__ no tienen propiedades, las consultas de solo claves y las que no son de solo claves muestran la misma información.

Las entidades del espacio de nombres son instancias de la clase de modelo google.appengine.ext.db.metadata.Namespace. La propiedad de string namespace_name, calculada a partir de la clave de la entidad, muestra el nombre del espacio de nombres correspondiente (si la clave tiene un ID numérico 1, la propiedad muestra la string vacía). Para facilitar la consulta, el modelo Namespace proporciona los siguientes métodos de clase:

A modo de ejemplo, a continuación, se muestra la implementación de la función auxiliar get_namespaces(), que muestra una lista que contiene los nombres de todos los espacios de nombres de una aplicación (o aquellos en el rango 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 tipos

Las consultas de categorías muestran entidades de categoría __kind__ cuyo nombre de clave es el nombre de una categoría de entidad. Las consultas de este tipo están restringidas de forma implícita al espacio de nombres actual y admiten el filtrado solo para rangos sobre la seudopropiedad __key__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Debido a que las entidades __kind__ no tienen propiedades, las consultas de solo claves y las que no son de solo claves muestran la misma información.

Las entidades de categoría son instancias de la clase de modelo google.appengine.ext.db.metadata.Kind. La propiedad de la string kind_name, calculada a partir de la clave de la entidad, muestra el nombre de la categoría de entidad correspondiente. Para facilitar la consulta, el modelo Kind proporciona los siguientes métodos de clase:

  • Kind.key_for_kind() compila una clave __kind__ a partir de un nombre de categoría.
  • Kind.key_to_kind() muestra el nombre de categoría correspondiente a una clave __kind__ determinada.

A modo de ejemplo, a continuación, se muestra la implementación de la función auxiliar get_kinds(), que muestra una lista que contiene los nombres de todas las categorías de entidad de una aplicación (o aquellos en el rango 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 muestran todas las categorías cuyos nombres comienzan con 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 muestran entidades de categoría __property__ que denotan las propiedades asociadas con una categoría de entidad (sin importar si están definidas en la actualidad en el modelo de la categoría). La entidad que representa la propiedad P de la categoría K está compuesta de la siguiente manera:

  • La clave de la entidad tiene el tipo __property__ y el nombre de clave P.
  • La clave de la entidad principal tiene la categoría __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 string kind_name y property_name, calculadas a partir de la clave de la entidad, muestran los nombres de la categoría y la propiedad correspondientes. El modelo Property proporciona cuatro métodos de clase para simplificar la compilación y el examen de las 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 de solo claves o no de solo claves (representación de propiedad), como se detalla en las subsecciones que siguen.

Consultas de propiedades: de solo claves

Las consultas de propiedad de solo claves muestran una clave para cada propiedad indexada de un tipo de entidad especificado. (No se incluyen las propiedades no indexadas). En el siguiente ejemplo, se muestran los nombres de todas las categorías de entidades de una aplicación y las propiedades asociadas a cada una:

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 de forma implícita al espacio de nombres actual y solo admiten filtros para rangos superiores a la seudopropiedad __key__, en los que las claves denotan entidades __kind__ o __property__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Los filtros se aplican a los pares tipo-propiedad, ordenados primero por tipo y después por propiedad. Por ejemplo, imagina que tienes una entidad con las siguientes 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 que muestra los datos de la propiedad se verá de la siguiente manera:

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 mostraría lo siguiente:

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

Observa que los resultados no incluyen la propiedad name de tipo Employee, la propiedad title de tipo Manager, ni ninguna propiedad de tipos Account y Product, porque quedan fuera del rango especificado para la consulta.

Las consultas de propiedades también admiten el filtrado principal en una clave __kind__ o __property__ para limitar los resultados de las consultas a un solo tipo o propiedad. Puedes usar esto, por ejemplo, para obtener las propiedades asociadas a una categoría de entidad determinada, 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 propiedad: no de solo claves (representación de propiedad)

Las consultas de propiedades de no solo claves, conocidas como consultas de representación de propiedad, muestran información adicional sobre las representaciones que usa cada par tipo-propiedad. (no se incluyen las propiedades no indexadas). La entidad que se muestra para la propiedad P de tipo K tiene la misma clave que la de una consulta de solo claves correspondiente, junto con una propiedad property_representation adicional que muestra las representaciones de la propiedad. El valor de esta propiedad es una instancia de la clase StringListProperty que contiene una string para cada representación de propiedad P que se encuentra en cualquier entidad de la categoría K.

Ten en cuenta que las representaciones no son las mismas que las clases de propiedad; varias clases de propiedad se pueden asignar a la misma representación (por ejemplo, StringProperty y PhoneNumberProperty usan la representación STRING).

La siguiente tabla muestra las asignaciones entre las clases de propiedad y sus representaciones:

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

A modo de ejemplo, a continuación, se muestra la implementación de la función auxiliar get_representations_of_kind(), que muestra un diccionario que contiene las representaciones de todas las propiedades indexadas de una aplicación (o las que están en el rango entre dos nombres especificados, start y end) que se asocian con una categoría de entidad determinada. El diccionario asigna el nombre de cada propiedad a una lista de las 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