【问题标题】:App Engine (Python) Datastore Precall API HooksApp Engine (Python) 数据存储区预调用 API 挂钩
【发布时间】:2010-02-28 16:50:34
【问题描述】:

背景

假设我正在为 GAE 制作应用程序,并且我想使用 API Hooks。

大编辑:在这个问题的原始版本中,我描述了我的用例,但有些人正确地指出它并不真正适合 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 困扰,我使用 GAEUnit 完成所有这些,所以在 gaeunit.py 中,就在 main 方法上方,我添加:

import db_hooks
db_hooks.patch_appengine()

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


问题

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

【问题讨论】:

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


    【解决方案1】:

    对于手头的任务来说,Hooks 的级别有点低。您可能想要的是自定义属性类。来自aetycoon 的 DerivedProperty 就是门票。

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

    【讨论】:

      【解决方案2】:

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

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

      就我所知,Monkey 修补所有各种 put/delete 方法是设置模型级回调的最佳方式†

      由于似乎没有太多关于如何使用较新的异步调用安全地执行此操作的资源,因此这里有一个实现 before_put、after_put、before_delete 和 after_delete 钩子的 BaseModel:

      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() 等方法保存或销毁您的实例,并获得所需的效果。

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

      【讨论】:

        【解决方案3】:

        我认为 Hooks 并不能真正解决这个问题。 Hooks 只会在您的 AppEngine 应用程序的上下文中运行,但用户可以使用 Google 帐户设置在您的应用程序之外更改他们的昵称。如果他们这样做,它不会触发你的钩子中的任何逻辑实现。

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

        【讨论】:

        • 啊,该死的,你说得对。那么如何强制每个帐户的唯一性呢?在看跌期权之前检查不是最好的吗?
        • 如果您想强制执行唯一性,那么您必须在第一次放入数据存储区中创建实体之前进行检查。如果您禁止用户更改该值(这是个好主意),您只需检查创建。
        • 是的,在这两种情况下,我只是要编写一个方法,在放置之前执行我想要的检查和更新,然后使用它而不是 put()。谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-16
        • 2013-12-03
        • 1970-01-01
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        相关资源
        最近更新 更多