Métadonnées

Remarque : Les développeurs qui créent des applications sont vivement encouragés à utiliser la bibliothèque cliente NDB qui présente plusieurs avantages supplémentaires par rapport à cette bibliothèque cliente, tels que la mise en cache automatique des entités via l'API Memcache. Si vous utilisez actuellement l'ancienne bibliothèque cliente DB, consultez le guide de migration de DB vers NDB.

Datastore offre un accès automatisé à certaines de ses métadonnées de façon à permettre la métaprogrammation, à mettre en œuvre des fonctions d'administration de backend, à simplifier la mise en cache cohérente et à des fins semblables. Par exemple, vous pouvez vous en servir pour créer une visionneuse Datastore personnalisée pour votre application. Les métadonnées disponibles incluent des informations sur les groupes d'entités, les espaces de noms, les genres d'entités et les propriétés qu'emploie l'application, ainsi que sur les représentations de chaque propriété.

Le tableau de bord Datastore de la console Google Cloud fournit également des métadonnées sur votre application, mais les données affichées diffèrent nettement à certains égards de celles renvoyées par ces fonctions.

  • Actualisation. La lecture des métadonnées à l'aide de l'API permet d'obtenir les données actuelles, tandis que les données du tableau de bord ne sont actualisées qu'une fois par jour.
  • Contenus. Certaines métadonnées du tableau de bord ne sont pas disponibles via les API, et vice versa.
  • Rapidité. Les opérations d'obtention et de requête de métadonnées sont facturées de la même manière que celles appliquées à un datastore. L'exécution de requêtes de métadonnées qui extraient des informations sur les espaces de noms, les genres et les propriétés est habituellement lente. En règle générale, partez du principe qu'une requête de métadonnées renvoyant N entités va prendre à peu près le même temps que N requêtes ordinaires renvoyant chacune une seule entité. De plus, les requêtes de représentation de propriété (requêtes de propriété ne contenant pas que des clés) sont plus lentes que les requêtes de propriété ne contenant que des clés. L'obtention des métadonnées d'un groupe d'entités est légèrement plus rapide que pour les métadonnées d'une entité standard.

Fonctions de l'outil d'aide

Les fonctions suivantes permettent d'obtenir des informations de métadonnées :

  • get_entity_group_version() obtient un numéro de version pour un groupe d'entités. Cette information est utile pour savoir si une entité du groupe a changé depuis la dernière demande de numéro de version.
  • get_namespaces() renvoie une liste contenant les noms de tous les espaces de noms d'une application ou ceux d'une plage spécifiée.
  • get_kinds() renvoie une liste contenant les noms de tous les genres d'entités d'une application ou ceux d'une plage spécifiée.
  • get_properties_of_kind() renvoie une liste contenant les noms de toutes les propriétés indexées d'une application (ou de celles d'une plage spécifiée) associées à un genre d'entité donné. Les propriétés non indexées ne sont pas incluses.
  • get_representations_of_kind() renvoie un dictionnaire contenant les représentations de toutes les propriétés indexées d'une application ou de celles d'une plage spécifiée associées à un genre d'entité donné. Le dictionnaire mappe le nom de chaque propriété à une liste des représentations de cette propriété. Les propriétés non indexées ne sont pas incluses.

Métadonnées de groupe d'entités

Cloud Datastore donne accès à la "version" d'un groupe d'entités, un nombre strictement positif dont l'augmentation est garantie à chaque modification du groupe d'entités.

L'exemple suivant illustre comment obtenir la version d'un groupe d'entités :

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)

Ancien comportement

Lorsque vous conservez l'ancien comportement d'une version de groupe d'entités, celle-ci n'augmente que si des modifications sont apportées au groupe d'entités. L'ancien comportement des métadonnées d'un groupe d'entités peut servir, par exemple, à assurer la cohérence des résultats mis en cache générés par une requête ascendante complexe au sein d'un groupe d'entités.

Cet exemple met en cache les résultats (un nombre de résultats) d'une requête et sollicite l'ancien comportement des versions de groupe d'entités pour utiliser la valeur mise en cache si elle est actuelle :

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() peut renvoyer None pour un groupe d'entités sur lequel aucune écriture n'a été effectuée.

Les versions de groupe d'entités s'obtiennent en appelant get() sur une pseudo-entité spéciale contenant une propriété __version__. Pour en savoir plus, consultez la documentation de référence sur EntityGroup.

Requêtes de métadonnées

Si les fonctions de l'outil d'aide décrites dans la section précédente ne répondent pas à vos besoins, vous pouvez émettre des requêtes de métadonnées plus élaborées ou plus flexibles en utilisant une requête de métadonnées explicite. Avec Python, les classes de modèle de ces requêtes sont définies dans le package google.appengine.ext.db.metadata. Ces modèles fournissent des genres d'entités spéciaux réservés aux requêtes de métadonnées :

Classe de modèle Genre d'entité
Namespace __namespace__
Kind __kind__
Property __property__

Ces modèles et ces genres n'entreront pas en conflit avec d'autres du même nom qui peuvent déjà exister dans votre application. En exécutant des requêtes sur ces genres spéciaux, vous pouvez récupérer des entités contenant les métadonnées souhaitées.

Les entités renvoyées par les requêtes de métadonnées sont générées de manière dynamique, en fonction de l'état actuel de Datastore. Bien que vous puissiez créer des instances locales des classes de modèle Namespace, Kind, ou Property, toute tentative de stockage dans Datastore échouera, générant une exception BadRequestError.

Vous pouvez émettre des requêtes de métadonnées à l'aide d'un objet de requête appartenant à l'une des deux classes suivantes :

  • Un objet Query renvoyé par la méthode de classe Namespace.all(), Kind.all() ou Property.all() (héritée de la méthode de superclasse Model.all())
  • Un objet GqlQuery pour les requêtes de style GQL

L'exemple suivant renvoie les noms de tous les genres d'entités qui se trouvent dans une application :

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

Requêtes d'espace de noms

Si votre application utilise l'API Namespaces, vous pouvez exécuter une requête d'espace de noms pour rechercher tous les espaces de noms utilisés dans les entités de l'application. Cela vous permet par exemple d'effectuer des tâches d'administration sur plusieurs espaces de noms.

Les requêtes d'espace de noms renvoient des entités du genre spécial __namespace__, dont le nom de clé correspond à celui d'un espace de noms. (L'espace de noms par défaut désigné par la chaîne vide "" constitue une exception : comme la chaîne vide n'est pas un nom de clé valide, cet espace de noms est associé à l'ID numérique 1 à la place.) Les requêtes de ce type n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété spéciale __key__, dont la valeur est la clé de l'entité. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Comme les entités __namespace__ n'ont pas de propriétés, les requêtes renvoient les mêmes informations, qu'elles contiennent exclusivement des clés ou non.

Les entités d'espace de noms sont des instances de la classe de modèle google.appengine.ext.db.metadata.Namespace. La propriété de chaîne namespace_name, calculée à partir de la clé de l'entité, renvoie le nom de l'espace de noms correspondant. (Si l'identifiant numérique de la clé est 1, la propriété renvoie une chaîne vide.) Pour faciliter les requêtes, le modèle Namespace fournit les méthodes de classe suivantes :

Voici un exemple de mise en œuvre de la fonction get_namespaces() de l'outil d'aide. Celle-ci renvoie une liste contenant les noms de tous les espaces de noms d'une application (ou ceux compris dans la plage entre les deux noms spécifiés, start et 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]

Requêtes de genre

Les requêtes de genre renvoient des entités du genre __kind__, dont le nom de clé correspond à celui d'un genre d'entité. Les requêtes de ce type sont implicitement restreintes à l'espace de noms actuel et n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété __key__. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Comme les entités __kind__ n'ont pas de propriétés, les requêtes renvoient les mêmes informations, qu'elles contiennent exclusivement des clés ou non.

Les entités de genre sont des instances de la classe de modèle google.appengine.ext.db.metadata.Kind. La propriété de chaîne kind_name, calculée à partir de la clé de l'entité, renvoie le nom du genre d'entité correspondant. Pour faciliter les requêtes, le modèle Kind fournit les méthodes de classe suivantes :

Voici un exemple de mise en œuvre de la fonction get_kinds() de l'outil d'aide. Celle-ci renvoie une liste contenant les noms de tous les genres d'entités d'une application (ou ceux compris dans la plage entre les deux noms spécifiés, start et 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]

L'exemple suivant renvoie tous les genres dont le nom commence par une lettre minuscule :

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

Requêtes de propriété

Les requêtes de propriété renvoient des entités du genre __property__ indiquant les propriétés associées à un genre d'entité (que ces propriétés soient ou non actuellement définies dans le modèle du genre). L'entité représentant la propriété P du genre K est conçue comme suit :

  • La clé de l'entité est du genre __property__ et porte le nom p.
  • La clé de l'entité parente est du genre __kind__ et porte le nom K.

Les entités de propriété sont des instances de la classe de modèle google.appengine.ext.db.metadata.Property. Les propriétés de chaîne kind_name et property_name, calculées à partir de la clé de l'entité, renvoient les noms du genre et de la propriété correspondants. Le modèle Property propose quatre méthodes de classe pour simplifier la création et l'examen des clés __property__ :

L'exemple suivant illustre ces méthodes :

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"

Une requête de propriété se comporte différemment selon qu'il s'agit d'une requête ne contenant que des clés ou d'une requête ne contenant pas que des clés (représentation de propriétés), comme détaillé dans les sous-sections ci-dessous.

Requêtes de propriété ne contenant que des clés

Les requêtes de propriété ne contenant que des clés renvoient une clé pour chaque propriété indexée d'un genre d'entité spécifié. (Les propriétés non indexées ne sont pas incluses.) L'exemple suivant imprime les noms de tous les genres d'entités d'une application et les propriétés associées à chacun d'eux :

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

Les requêtes de ce type sont implicitement restreintes à l'espace de noms actuel et n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété __key__, lorsque les clés désignent des entités __kind__ ou __property__. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Le filtrage est appliqué aux paires genre-propriété, triées d'abord par genre et ensuite par propriété. Supposons, par exemple, qu'une entité ait les propriétés suivantes :

  • genre Account avec des propriétés
    • balance
    • company
  • genre Employee avec des propriétés
    • name
    • ssn
  • genre Invoice avec des propriétés
    • date
    • amount
  • genre Manager avec des propriétés
    • name
    • title
  • genre Product avec des propriétés
    • description
    • price

La requête qui doit renvoyer les données de propriété ressemblerait à ceci :

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 requête ci-dessus renverrait les éléments suivants :

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

Notez que les résultats n'incluent pas la propriété name du genre Employee, ni la propriété title du genre Manager, ni aucune propriété du genre Account ou Product, car elles ne sont pas comprises dans la plage spécifiée pour la requête.

Les requêtes de propriété acceptent également le filtrage en fonction des ancêtres sur une clé __kind__ ou __property__, afin de limiter les résultats à un seul genre ou à une seule propriété. Vous pouvez, par exemple, suivre cette approche pour obtenir les propriétés associées à un genre d'entité donné, comme dans l'exemple suivant :

(une mise en œuvre de la fonction get_properties_of_kind() de l'outil d'aide)

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]

Requêtes de propriété ne contenant pas que des clés (représentation de propriété)

Les requêtes de propriété ne contenant pas que des clés, appelées requêtes de représentation de propriété, renvoient des informations supplémentaires sur les représentations utilisées par chaque paire genre-propriété. (Les propriétés non indexées ne sont pas incluses.) L'entité renvoyée pour une propriété P de genre K a la même clé qu'une requête correspondante ne contenant que des clés, mais également une propriété property_representation supplémentaire qui renvoie les représentations de la propriété. La valeur de cette propriété correspond à une instance de la classe StringListProperty contenant une chaîne pour chaque représentation de la propriété P trouvée dans une entité de genre K.

Notez que les représentations ne sont pas identiques aux classes de propriétés. Plusieurs classes de propriétés peuvent correspondre à la même représentation. (Par exemple, les classes StringProperty et PhoneNumberProperty utilisent toutes deux la représentation STRING.)

Le tableau suivant établit une correspondance entre les classes de propriétés et leurs représentations :

Classe de propriété Représentation
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 Représentation d'élément de liste
StringListProperty Représentation d'élément de liste

Voici un exemple de mise en œuvre de la fonction get_representations_of_kind() de l'outil d'aide. Celle-ci renvoie un dictionnaire contenant les représentations pour toutes les propriétés indexées d'une application (ou celles comprises dans la plage entre les deux noms spécifiés, start et end), associées à un genre d'entité donné. Le dictionnaire mappe le nom de chaque propriété sur une liste des représentations de cette propriété :

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