Créer des modèles d'entité

Vous devez créer une classe de modèle pour votre entité. Pour cela, vous avez le choix entre deux méthodes :

  • Créer une classe de modèle qui définit les propriétés de l'entité.
  • Créer une classe de modèle Expando qui ne définit pas les entités à l'avance.

Ce document explique comment créer chacun de ces types de classes de modèle. Il explique également comment créer un hook de modèle, afin que votre application puisse exécuter du code avant ou après un certain type d'opérations, par exemple avant chaque opération get().

Créer une classe de modèle avec des propriétés

Avant de créer une entité, vous devez créer une classe de modèle qui définit une ou plusieurs propriétés d'entité. Exemple :

from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

où chaque entité de compte va avoir des propriétés correspondant au nom d'utilisateur, à l'ID d'utilisateur et à l'adresse e-mail.

Pour obtenir une liste complète des types de propriétés, consultez la documentation de référence relative aux propriétés d'entité.

Créer une classe de modèle Expando

Vous n'avez pas l'obligation d'utiliser une classe de modèle qui définit les propriétés à l'avance. Une sous-classe de modèle spéciale nommée Expando modifie le comportement de ses entités, afin que tout attribut attribué soit enregistré dans le datastore. Notez que ces attributs ne peuvent pas commencer par un trait de soulignement.

Voici comment créer un modèle Expando :

class Mine(ndb.Expando):
    pass
...
e = Mine()
e.foo = 1
e.bar = 'blah'
e.tags = ['exp', 'and', 'oh']
e.put()

Ce code écrit dans le datastore une entité avec une propriété foo ayant la valeur entière 1, une propriété bar ayant la valeur de chaîne 'blah' et une propriété tag répétée avec les valeurs de chaîne 'exp', 'and' et 'oh'. Les propriétés sont indexées, et vous pouvez les inspecter à l'aide de l'attribut _properties de l'entité :

return e._properties
# {
#     'foo': GenericProperty('foo'),
#     'bar': GenericProperty('bar'),
#     'tags': GenericProperty('tags', repeated=True)
# }

Une sous-classe Expando créée en extrayant une valeur du datastore possède des propriétés pour toutes les valeurs de propriété enregistrées dans le datastore.

Une application peut ajouter des propriétés prédéfinies à une sous-classe Expando :

class FlexEmployee(ndb.Expando):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
...
employee = FlexEmployee(name='Sandy', location='SF')

Cela donne à employee un attribut name ayant la valeur 'Sandy', un attribut age ayant la valeur None et un attribut dynamique location ayant la valeur 'SF'.

Pour créer une sous-classe Expando dont les propriétés ne sont pas indexées, spécifiez _default_indexed = False dans la définition de la sous-classe :

class Specialized(ndb.Expando):
    _default_indexed = False
...
e = Specialized(foo='a', bar=['b'])
return e._properties
# {
#     'foo': GenericProperty('foo', indexed=False),
#     'bar': GenericProperty('bar', indexed=False, repeated=True)
# }

Vous pouvez également définir _default_indexed sur une entité Expando. Dans ce cas, toutes les propriétés attribuées après la configuration seront affectées.

Une autre technique utile consiste à interroger un genre Expando pour une propriété dynamique. Une requête comme celle-ci :

FlexEmployee.query(FlexEmployee.location == 'SF')

ne fonctionnera pas, car la classe n'a pas d'objet de propriété pour la propriété d'emplacement. Optez plutôt pour GenericProperty, la classe que Expando utilise pour les propriétés dynamiques :

FlexEmployee.query(ndb.GenericProperty('location') == 'SF')

Utiliser des hooks de modèle

NDB offre un mécanisme de hooking léger. Lorsqu'un hook est défini, une application peut exécuter du code avant ou après un certain type d'opérations. Par exemple, un Model peut exécuter une fonction avant chaque opération get().

Une fonction de hook s'exécute lors de l'utilisation des versions synchrones, asynchrones et multi de la méthode correspondante. Par exemple, un hook "pré-get" s'appliquerait à toutes les opérations get(), get_async() et get_multi(). Il existe des versions pré-RPC et post-RPC de chaque hook.

Les hooks peuvent être utiles pour les opérations suivantes :

  • Mise en cache de requêtes
  • Audit de l'activité Cloud Datastore par utilisateur
  • Imitation de déclencheurs de base de données

L'exemple suivant montre comment définir des fonctions de hook :

from google.appengine.ext import ndb
...
class Friend(ndb.Model):
    name = ndb.StringProperty()

    def _pre_put_hook(self):
        _notify('Gee wiz I have a new friend!')

    @classmethod
    def _post_delete_hook(cls, key, future):
        _notify('I have found occasion to rethink our friendship.')
...
f = Friend()
f.name = 'Carole King'
f.put()  # _pre_put_hook is called
fut = f.key.delete_async()  # _post_delete_hook not yet called
fut.get_result()  # _post_delete_hook is called

Si vous utilisez des post-hooks avec des API asynchrones, ceux-ci sont déclenchés en appelant check_result() ou get_result(), ou en appliquant "yield" (dans un tasklet) à l'objet "Future" d'une méthode asynchrone. Les post-hooks ne vérifient pas si le RPC a abouti, ils s'exécutent même en cas d'échec.

Tous les post-hooks disposent d'un argument Future à la fin de la signature de l'appel. Cet objet Future contient le résultat de l'action. Vous pouvez appeler get_result() sur cet objet Future pour récupérer le résultat. Vous êtes ainsi certain que get_result() ne se bloquera pas, car l'objet Future est terminé au moment où le hook est appelé.

La génération d'une exception lors d'un pré-hook empêche la requête d'avoir lieu. Bien que les hooks soient déclenchés à l'intérieur des méthodes <var>*</var>_async, vous ne pouvez pas préempter un RPC en générant tasklets.Return dans un hook pré-RPC.

Étapes suivantes