2010-02-28 5 views
5

HintergrundApp Engine (Python) Datastore precall API Hooks

Also lassen Sie uns sagen, ich mache App für GAE, und ich möchte API Hooks verwenden.

BIG EDIT: In der ursprünglichen Version dieser Frage beschrieb ich meinen Anwendungsfall, aber einige Leute richtig darauf hingewiesen, dass es nicht wirklich für API-Hooks geeignet war. Gewährt! Betrachte mich als geholfen. Aber jetzt ist mein Thema akademisch: Ich weiß immer noch nicht, wie man Hooks in der Praxis benutzt, und ich würde es gerne tun. Ich habe meine Frage neu geschrieben, um sie allgemeiner zu machen.


-Code

Also mache ich ein Modell wie folgt aus:

class Model(db.Model): 
    user = db.UserProperty(required=True) 
    def pre_put(self): 
     # Sets a value, raises an exception, whatever. Use your imagination 

Und dann erstelle ich eine db_hooks.py:

from google.appengine.api import apiproxy_stub_map 

def patch_appengine(): 
    def hook(service, call, request, response): 
     assert service == 'datastore_v3' 
     if call == 'Put': 
      for entity in request.entity_list(): 
       entity.pre_put() 

    apiproxy_stub_map.apiproxy.GetPreCallHooks().Append('preput', 
                 hook, 
                 'datastore_v3') 

Being TDD-verwirrten, Ich mache das alles mit GAEUnit, also in gaeunit.py, knapp über t Er Hauptmethode, ich füge hinzu:

Und dann schreibe ich einen Test, der instanziiert und setzt ein Modell.


Frage

Während patch_appengine() definitiv genannt wird, ist der Haken nie. Was vermisse ich? Wie kann ich die pre_put-Funktion tatsächlich aufrufen lassen?

Antwort

1

Ich glaube nicht, dass Hooks dieses Problem wirklich lösen werden. Die Hooks werden nur im Kontext Ihrer AppEngine-Anwendung ausgeführt, aber der Nutzer kann seinen Spitznamen außerhalb der Anwendung mithilfe der Google-Kontoeinstellungen ändern. Wenn sie das tun, wird es keine Logik in Ihren Hooks auslösen.

Ich denke, dass die wahre Lösung für Ihr Problem darin besteht, dass Ihre Anwendung ihren eigenen Nickname verwaltet, der unabhängig von demjenigen ist, der von der Entität Users verfügbar gemacht wird.

+0

Ah, verdammt, du hast absolut recht. Wie steht es mit der Durchsetzung der Einzigartigkeit pro Konto? Wäre das nicht am besten, indem Sie vor einem Put überprüfen? –

+0

Wenn Sie die Eindeutigkeit erzwingen möchten, müssen Sie dies vor dem ersten put überprüfen, der die Entität im Datenspeicher erstellt. Wenn Sie dem Benutzer verbieten, diesen Wert zu ändern - was eine gute Idee ist - müssen Sie nur die Erstellung überprüfen. –

+0

Wahr, und in beiden Fällen werde ich nur eine Methode schreiben, die die Überprüfungen und Aktualisierungen durchführt, die ich will, bevor ich setze, und dann diese anstelle von put() verwenden. Vielen Dank. –

2

Haken sind ein wenig niedriger Ebene für die vorliegende Aufgabe. Was Sie wahrscheinlich wollen, ist eine benutzerdefinierte Eigenschaft Klasse. DerivedProperty, von aetycoon, ist nur das Ticket.

Beachten Sie jedoch, dass das 'Nickname' Feld des Benutzerobjekts wahrscheinlich nicht das ist, was Sie wollen - per the docs, es ist einfach der Benutzer Teil des E-Mail-Felds, wenn sie ein Google Mail-Konto verwenden, sonst ist es ihre vollständige E-Mail-Adresse. Wahrscheinlich möchten Sie den Nutzern stattdessen erlauben, ihre eigenen Nicknames zu setzen.

2

Das Problem hier ist, dass im Zusammenhang mit der hook()-Funktion eine entity keine Instanz von db.Model ist, wie Sie erwarten.

In diesem Zusammenhang ist entity die Protokollpufferklasse, die verwirrend als Entität (entity_pb) bezeichnet wird.Stellen Sie sich das wie eine JSON-Repräsentation Ihrer realen Entität vor, alle Daten sind dort und Sie könnten eine neue-Instanz daraus erstellen, aber es gibt keinen Verweis auf Ihre speicherresidente Instanz, die auf ihren Rückruf wartet.

Affen all verschiedenen put/delete Methoden Patchen ist der beste Weg, um Setup-Modell-Ebene Rückrufe soweit ich weiß, †

Da es nicht so viele Ressourcen zu sein scheint, wie dies mit Sicherheit zu tun die neueren Asynchron-Anrufe, hier ist ein Basemodel, die before_put implementiert, after_put, before_delete & after_delete Haken:

class HookedModel(db.Model): 

    def before_put(self): 
     logging.error("before put") 

    def after_put(self): 
     logging.error("after put") 

    def before_delete(self): 
     logging.error("before delete") 

    def after_delete(self): 
     logging.error("after delete") 

    def put(self): 
     return self.put_async().get_result() 

    def delete(self): 
     return self.delete_async().get_result() 

    def put_async(self): 
     return db.put_async(self) 

    def delete_async(self): 
     return db.delete_async(self) 

Ihre Modell-Klassen von HookedModel Inherit und die before_xxx außer Kraft setzen, after_xxx Methoden je nach Bedarf.

Platzieren Sie den folgenden Code irgendwo, der global in Ihre Anwendung geladen wird (wie main.py, wenn Sie ein hübsches Standardlayout verwenden). Dies ist der Teil, der unseren Haken ruft:

def normalize_entities(entities): 
    if not isinstance(entities, (list, tuple)): 
     entities = (entities,) 
    return [e for e in entities if hasattr(e, 'before_put')] 

# monkeypatch put_async to call entity.before_put 
db_put_async = db.put_async 
def db_put_async_hooked(entities, **kwargs): 
    ents = normalize_entities(entities) 
    for entity in ents: 
     entity.before_put() 
    a = db_put_async(entities, **kwargs) 
    get_result = a.get_result 
    def get_result_with_callback(): 
     for entity in ents: 
      entity.after_put() 
     return get_result() 
    a.get_result = get_result_with_callback 
    return a 
db.put_async = db_put_async_hooked 


# monkeypatch delete_async to call entity.before_delete 
db_delete_async = db.delete_async 
def db_delete_async_hooked(entities, **kwargs): 
    ents = normalize_entities(entities) 
    for entity in ents: 
     entity.before_delete() 
    a = db_delete_async(entities, **kwargs) 
    get_result = a.get_result 
    def get_result_with_callback(): 
     for entity in ents: 
      entity.after_delete() 
     return get_result() 
    a.get_result = get_result_with_callback 
    return a 
db.delete_async = db_delete_async_hooked 

Sie können Ihre Instanzen über model.put() oder einer der db.put(), db.put_async() etc, Methoden und erhält die retten oder zerstören gewünschter Effekt.

† würde gerne wissen, ob es eine noch bessere Lösung gibt !?

Verwandte Themen