Python 2 wird von der Community nicht mehr unterstützt. Wir empfehlen die Migration von Python 2-Anwendungen zu Python 3.

Entitätsmodelle erstellen

Sie müssen eine Modellklasse für Ihre Entität erstellen. Dafür haben Sie die folgenden beiden Möglichkeiten:

  • Eine Modellklasse erstellen, die Entitäts-Properties definiert.
  • Eine Expando-Modellklasse erstellen, die keine Entitäten im Voraus definiert.

In diesem Dokument wird gezeigt, wie Sie diese beiden Arten von Modellklassen erstellen. Es wird dabei auch erläutert, wie Sie einen Modell-Hook erstellen, mit dem Ihre Anwendung vor oder nach bestimmten Vorgängen, z. B. vor jeder get()-Methode, Code ausführen kann.

Modellklassen mit Attributen erstellen

Bevor Sie eine Entität erstellen, müssen Sie eine Modellklasse anlegen, die eine oder mehrere Entitätsattribute definiert. Beispiel:

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

Dabei soll jede Kontenentität Attribute für den Nutzernamen, die Nutzer-ID und die E-Mail-Adresse haben.

Eine vollständige Liste der Attributtypen finden Sie unter Objektattribute – Referenz.

Expando-Modellklassen erstellen

Sie müssen keine Modellklassen verwenden, die Attribute im Voraus definieren. Eine spezielle abgeleitete Modellklasse mit dem Namen Expando ändert das Verhalten ihrer Entitäten, sodass alle zugewiesenen Attribute im Datenspeicher abgelegt werden. Beachten Sie, dass solche Attribute nicht mit einem Unterstrich beginnen dürfen.

So erstellen Sie ein Expando-Modell:

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

Damit wird eine Entität mit einem foo-Attribut und dessen ganzzahligen Wert 1, mit einem bar-Attribut und dessen Stringwert 'blah' sowie mit einem wiederholten tag-Attribut und dessen Stringwerten 'exp', 'and' und 'oh' in den Datenspeicher geschrieben. Die Attribute werden indexiert und können mit dem Attribut _properties der Entität überprüft werden:

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

Ein Expando-Modell, das durch Abruf eines Werts aus dem Datenspeicher erstellt wird, hat Attribute für alle Attributwerte, die im Datenspeicher gespeichert sind.

Eine Anwendung kann einer abgeleiteten Klasse Expando vordefinierte Attribute hinzufügen:

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

Damit erhält employee das Attribut name mit dem Wert 'Sandy', ein age-Attribut mit dem Wert None und ein dynamisches Attribut location mit dem Wert 'SF'.

Zum Erstellen einer abgeleiteten Klasse Expando, deren Attribute nicht indexiert sind, legen Sie in der Definition der abgeleiteten Klasse _default_indexed = False fest:

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

Sie können auch _default_indexed für eine Expando-Entität festlegen. In diesem Fall wirkt sich dies auf alle Attribute aus, die nach dem Festlegen zugewiesen wurden.

Eine andere hilfreiche Technik ist das Abfragen einer Expando-Art nach einem dynamischen Attribut. Eine Abfrage wie

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

kann nicht funktionieren, da die Klasse kein Attributobjekt für das Standortattribut hat. Verwenden Sie stattdessen GenericProperty, d. h. die Klasse, die Expando für dynamische Attribute nutzt:

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

Modell-Hooks verwenden

NDB bietet ein einfaches Hook-Verfahren. Durch Definition eines Hooks kann eine Anwendung Code vor oder nach bestimmten Vorgängen ausführen. Beispielsweise lässt sich mit Model vor jeder get()-Methode eine Funktion ausführen.

Eine Hook-Funktion wird ausgeführt, wenn synchrone, asynchrone und mehrere Versionen der entsprechenden Methode verwendet werden. Beispiel: Ein "Pre-Get"-Hook gilt für alle get()-, get_async()- und get_multi()-Methoden. Für jeden Hook gibt es Pre-RPC- und Post-RPC-Versionen.

Hooks können für Folgendes nützlich sein:

  • Abfrage-Caching
  • Überwachen der Cloud Datastore-Aktivität pro Nutzer
  • Datenbank-Trigger nachahmen

Das folgende Beispiel zeigt, wie Hook-Funktionen definiert werden:

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

Wenn Sie Post-Hooks mit asynchronen APIs verwenden, werden die Hooks durch Aufruf von check_result() oder get_result() oder durch die Ausgabe des Future-Arguments einer asynchronen Methode (in einem Tasklet) ausgelöst. Post-Hooks prüfen nicht, ob der RPC erfolgreich war. Der Hook wird auch bei Fehlern ausgeführt.

Alle Post-Hooks enthalten das Argument Future am Ende der Aufrufsignatur. Dieses Future-Objekt beinhaltet das Ergebnis der Aktion. Sie können für den Abruf des Ergebnisses die Methode get_result() für dieses Future-Objekt aufrufen. get_result() blockiert dabei nicht, da das Future-Objekt zum Zeitpunkt des Hook-Aufrufs vollständig ist.

Durch Auslösen einer Ausnahme während eines Pre-Hooks wird verhindert, dass die Anfrage ausgeführt wird. Obwohl Hooks innerhalb von <var>*</var>_async-Methoden ausgelöst werden, können Sie einen RPC nicht vorab ausführen, indem Sie tasklets.Return in einem Pre-RPC-Hook auslösen.

Nächste Schritte