App Engine(Python)数据存储区Precall API挂钩

时间:2010-02-28 16:50:35

标签: python google-app-engine google-cloud-datastore

背景

所以,假设我正在为GAE制作应用,我想使用API Hooks

BIG EDIT :在这个问题的原始版本中,我描述了我的用例,但有些人正确地指出它并不适合API Hooks。诚然!考虑我的帮助。但现在我的问题是学术性的:我仍然不知道如何在实践中使用钩子,我想。我已经重写了我的问题,使其更加通用。


代码

所以我制作了这样的模型:

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-addled,我正在使用GAEUnit完成所有这些,所以在gaeunit.py中,在main方法的上方,我添加:

import db_hooks
db_hooks.patch_appengine()

然后我编写了一个实例化并放置模型的测试。


问题

虽然肯定会调用patch_appengine(),但钩子永远不会被调用。我错过了什么?如何实际调用pre_put函数?

3 个答案:

答案 0 :(得分:2)

钩子对于手头的任务来说有点低级别。你可能想要的是一个自定义属性类。来自aetycoon的DerivedProperty只是门票。

但请记住,用户对象的“昵称”字段可能不是您想要的 - 每the docs,如果他们使用的是Gmail帐户,则只是电子邮件字段的用户部分,否则这是他们的完整电子邮件地址。您可能希望让用户设置自己的昵称。

答案 1 :(得分:2)

这里的问题是,在hook()函数的上下文中,entity不是您期望的db.Model实例。

在此上下文中,entity是协议缓冲类,容易混淆为实体(entity_pb)。可以把它想象成真实实体的JSON表示,所有数据都在那里,你可以从中构建一个 new 实例,但没有引用你正在等待的内存驻留实例这是回调。

根据我所知,猴子修补所有各种put/delete方法是设置模型级回调的最佳方法†

由于对于如何使用较新的异步调用安全地执行此操作似乎没有那么多资源,这里是一个实现before_put,after_put,before_delete&的BaseModel。 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()等方法保存或销毁您的实例,并获得所需的效果。

†很想知道是否有更好的解决方案!

答案 2 :(得分:1)

我不认为胡克斯真的会解决这个问题。 Hooks仅在AppEngine应用程序的上下文中运行,但用户可以使用Google帐户设置在应用程序之外更改其昵称。如果他们这样做,它将不会触发你的钩子中的任何逻辑工具。

我认为您问题的真正解决方案是让您的应用程序管理自己的昵称,该昵称与用户实体公开的昵称无关。