App Engine (Python) Хранилища API Precall для хранилища данных - PullRequest
5 голосов
/ 28 февраля 2010

Фон

Допустим, я создаю приложение для GAE и хочу использовать API-хуки .

БОЛЬШОЕ РЕДАКТИРОВАНИЕ : В оригинальной версии этого вопроса я описал свой вариант использования, но некоторые люди правильно отметили, что он не очень подходит для API-хуков.Предоставляется!Считай мне помогло.Но теперь моя проблема носит академический характер: я до сих пор не знаю, как использовать хуки на практике, и я бы хотел.Я переписал свой вопрос, чтобы сделать его гораздо более общим.


Код

Итак, я делаю такую ​​модель:

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

И затем я создаю 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')

Будучи добавленным в TDD, я делаю все это, используя GAEUnit , поэтому в gaeunit.py, чуть выше основногометод, я добавляю:

import db_hooks
db_hooks.patch_appengine()

А потом я пишу тест, который создает экземпляр и ставит модель.


Вопрос

Покаpatch_appengine() определенно вызывается, крюк никогда не вызывается.Что мне не хватает?Как заставить функцию pre_put вызываться?

Ответы [ 3 ]

2 голосов
/ 01 марта 2010

Крючки - это немного низкий уровень для поставленной задачи. То, что вы, вероятно, хотите, это пользовательский класс свойств. DerivedProperty, от aetycoon , просто билет.

Имейте в виду, однако, что поле 'псевдоним' пользовательского объекта, вероятно, не то, что вы хотите - на документы , это просто пользовательская часть поля электронной почты, если они учетная запись Gmail, в противном случае это их полный адрес электронной почты. Возможно, вы захотите позволить пользователям устанавливать свои собственные псевдонимы.

2 голосов
/ 23 мая 2011

Проблема здесь в том, что в контексте функции hook() entity не является экземпляром db.Model, как вы ожидаете.

В этом контексте entity - это класс буфера протокола, который до некоторой степени называют объектом ( entity_pb ). Думайте об этом как о представлении JSON вашей реальной сущности, все данные есть, и вы можете создать из нее новый экземпляр, но нет ссылки на ваш резидентный экземпляр, который ожидает его обратный вызов.

Обезьяна, исправляющая все различные put/delete методы, насколько я знаю, - лучший способ настроить обратные вызовы уровня модели †

Поскольку, кажется, не так много ресурсов, как безопасно сделать это с более новыми асинхронными вызовами, вот BaseModel, который реализует ловушки before_put, after_put, before_delete & after_delete:

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)

Унаследуйте ваши классы моделей от HookedModel и переопределите методы before_xxx, after_xxx, как требуется.

Поместите следующий код куда-нибудь, что будет загружено глобально в вашем приложении (например, main.py, если вы используете довольно стандартно выглядящий макет). Это та часть, которая вызывает наши хуки:

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

Вы можете сохранить или уничтожить свои экземпляры с помощью model.put () или любого из методов db.put (), db.put_async () и т. Д. И получить желаемый эффект.

† хотелось бы знать, есть ли еще лучшее решение!?

1 голос
/ 28 февраля 2010

Я не думаю, что Хукс действительно решит эту проблему.Хуки будут работать только в контексте вашего приложения AppEngine, но пользователь может изменить свой псевдоним вне вашего приложения, используя настройки аккаунта Google.Если они это сделают, это не вызовет никакой реализации логики в ваших хуках.

Я думаю, что реальное решение вашей проблемы - ваше приложение может управлять своим собственным псевдонимом, который не зависит от того, который выставляетсяПользовательский объект.

...