Rob Curtis
Rob Curtis

Reputation: 2265

How can you use async apis with ndb hooks?

I have some hooks in place, and I thought I could decorate them with @ndb.tasklet in order to use async apis inside the hooks. e.g.

@classmethod
@ndb.tasklet
def _post_delete_hook(cls, key,future):
    yield do_something_async()

This seemed to work, but every now and then I see "suspended generator" error for the code inside those hooks.

Should I be using @ndb.synctasklet instead?

An example of error:

suspended generator _post_put_hook(data_field.py:112) raised TypeError(Expected Future, received <class 'google.appengine.api.apiproxy_stub_map.UserRPC'>: <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x09AA00B0>)

The code causing the error occasionally was:

           t, d = yield (queue.add_async(task), queue.delete_tasks_async(taskqueue.Task(name=existing_task_name)))

Now that I've put @ndb.synctasklet it raises an actual exception.

Upvotes: 2

Views: 206

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55630

An ndb tasklet returns a future. If calling the tasklet results in an exception, the exception will only be raised if the future's get_result method is called.

ndb.synctasklet automatically calls get_result on the futures yielded by tasklets, causing exceptions to be raised if they occurred, rather than just logged.

For the error that you are seeing, you may be able to fix it by converting the UserRPCs returned by the taskqueue async methods to tasklets.

This untested code is based on ndb.context.urlfetch (link), which converts the UserRPC produced by urlfetch.createRPC into a Future.

  @ndb.tasklet
  def add_async(queue, **taskqueue_kwargs):
      rpc = queue.add_async(**taskqueue_kwargs)
      result = yield rpc
      raise ndb.Return(result)

You would need to create a tasklet for each async method that you want to use, or you could extend the taskqueue class and make the async methods tasklets.

Upvotes: 2

Related Questions