thirdparty/google_appengine/google/appengine/api/apiproxy_stub_map.py
changeset 2413 d0b7dac5325c
parent 828 f5fd65cc3bf3
child 2864 2e0b0af889be
--- a/thirdparty/google_appengine/google/appengine/api/apiproxy_stub_map.py	Mon Jun 08 22:34:05 2009 +0200
+++ b/thirdparty/google_appengine/google/appengine/api/apiproxy_stub_map.py	Fri Jun 19 16:13:32 2009 +0200
@@ -21,6 +21,7 @@
   APIProxyStubMap: container of APIProxy stubs.
   apiproxy: global instance of an APIProxyStubMap.
   MakeSyncCall: APIProxy entry point.
+  UserRPC: User-visible class wrapping asynchronous RPCs.
 """
 
 
@@ -30,6 +31,9 @@
 import inspect
 import sys
 
+from google.appengine.api import apiproxy_rpc
+
+
 def CreateRPC(service):
   """Creates a RPC instance for the given service.
 
@@ -160,7 +164,7 @@
         function(service, call, request, response)
 
 
-class APIProxyStubMap:
+class APIProxyStubMap(object):
   """Container of APIProxy stubs for more convenient unittesting.
 
   Stubs may be either trivial implementations of APIProxy services (e.g.
@@ -202,7 +206,7 @@
       service: string
       stub: stub
     """
-    assert not self.__stub_map.has_key(service)
+    assert not self.__stub_map.has_key(service), repr(service)
     self.__stub_map[service] = stub
 
     if service == 'datastore':
@@ -241,6 +245,203 @@
     self.__postcall_hooks.Call(service, call, request, response)
 
 
+class UserRPC(object):
+  """Wrapper class for asynchronous RPC.
+
+  Simplest low-level usage pattern:
+
+    rpc = UserRPC('service', [deadline], [callback])
+    rpc.make_call('method', request, response)
+    .
+    .
+    .
+    rpc.wait()
+    rpc.check_success()
+
+  However, a service module normally provides a wrapper so that the
+  typical usage pattern becomes more like this:
+
+    from google.appengine.api import service
+    rpc = service.create_rpc([deadline], [callback])
+    service.make_method_call(rpc, [service-specific-args])
+    .
+    .
+    .
+    rpc.wait()
+    result = rpc.get_result()
+
+  The service.make_method_call() function sets a service- and method-
+  specific hook function that is called by rpc.get_result() with the
+  rpc object as its first argument, and service-specific value as its
+  second argument.  The hook function should call rpc.check_success()
+  and then extract the user-level result from the rpc.result
+  protobuffer.  Additional arguments may be passed from
+  make_method_call() to the get_result hook via the second argument.
+  """
+
+  __method = None
+  __get_result_hook = None
+  __user_data = None
+  __postcall_hooks_called = False
+
+  def __init__(self, service, deadline=None, callback=None):
+    """Constructor.
+
+    Args:
+      service: The service name.
+      deadline: Optional deadline.  Default depends on the implementation.
+      callback: Optional argument-less callback function.
+    """
+    self.__service = service
+    self.__rpc = CreateRPC(service)
+    self.__rpc.deadline = deadline
+    self.__rpc.callback = callback
+
+  @property
+  def service(self):
+    """Return the service name."""
+    return self.__service
+
+  @property
+  def method(self):
+    """Return the method name."""
+    return self.__method
+
+  @property
+  def deadline(self):
+    """Return the deadline, if set explicitly (otherwise None)."""
+    return self.__rpc.deadline
+
+  def __get_callback(self):
+    """Return the callback attribute, a function without arguments.
+
+    This attribute can also be assigned to.  For example, the
+    following code calls some_other_function(rpc) when the RPC is
+    complete:
+
+      rpc = service.create_rpc()
+      rpc.callback = lambda: some_other_function(rpc)
+      service.make_method_call(rpc)
+      rpc.wait()
+    """
+    return self.__rpc.callback
+  def __set_callback(self, callback):
+    """Set the callback function."""
+    self.__rpc.callback = callback
+  callback = property(__get_callback, __set_callback)
+
+  @property
+  def request(self):
+    """Return the request protocol buffer object."""
+    return self.__rpc.request
+
+  @property
+  def response(self):
+    """Return the response protocol buffer object."""
+    return self.__rpc.response
+
+  @property
+  def state(self):
+    """Return the RPC state.
+
+    Possible values are attributes of apiproxy_rpc.RPC: IDLE, RUNNING,
+    FINISHING.
+    """
+    return self.__rpc.state
+
+  @property
+  def get_result_hook(self):
+    """Return the get-result hook function."""
+    return self.__get_result_hook
+
+  @property
+  def user_data(self):
+    """Return the user data for the hook function."""
+    return self.__user_data
+
+  def make_call(self, method, request, response,
+                get_result_hook=None, user_data=None):
+    """Initiate a call.
+
+    Args:
+      method: The method name.
+      request: The request protocol buffer.
+      response: The response protocol buffer.
+      get_result_hook: Optional get-result hook function.  If not None,
+        this must be a function with exactly one argument, the RPC
+        object (self).  Its return value is returned from get_result().
+      user_data: Optional additional arbitrary data for the get-result
+        hook function.  This can be accessed as rpc.user_data.  The
+        type of this value is up to the service module.
+
+    This function may only be called once per RPC object.  It sends
+    the request to the remote server, but does not wait for a
+    response.  This allows concurrent execution of the remote call and
+    further local processing (e.g., making additional remote calls).
+
+    Before the call is initiated, the precall hooks are called.
+    """
+    assert self.__rpc.state == apiproxy_rpc.RPC.IDLE, repr(self.state)
+    self.__method = method
+    self.__get_result_hook = get_result_hook
+    self.__user_data = user_data
+    apiproxy.GetPreCallHooks().Call(self.__service, method, request, response)
+    self.__rpc.MakeCall(self.__service, method, request, response)
+
+  def wait(self):
+    """Wait for the call to complete, and call callbacks.
+
+    This is the only time callback functions may be called.  (However,
+    note that check_success() and get_result() call wait().)   Waiting
+    for one RPC may cause callbacks for other RPCs to be called.
+    Callback functions may call check_success() and get_result().
+
+    Callbacks are called without arguments; if a callback needs access
+    to the RPC object a Python nested function (a.k.a. closure) or a
+    bound may be used.  To facilitate this, the callback may be
+    assigned after the RPC object is created (but before make_call()
+    is called).
+
+    Note: don't confuse callbacks with get-result hooks or precall
+    and postcall hooks.
+    """
+    assert self.__rpc.state != apiproxy_rpc.RPC.IDLE, repr(self.state)
+    if self.__rpc.state == apiproxy_rpc.RPC.RUNNING:
+      self.__rpc.Wait()
+    assert self.__rpc.state == apiproxy_rpc.RPC.FINISHING, repr(self.state)
+
+  def check_success(self):
+    """Check for success of the RPC, possibly raising an exception.
+
+    This function should be called at least once per RPC.  If wait()
+    hasn't been called yet, it is called first.  If the RPC caused
+    an exceptional condition, an exception will be raised here.
+    The first time check_success() is called, the postcall hooks
+    are called.
+    """
+    self.wait()
+    self.__rpc.CheckSuccess()
+    if not self.__postcall_hooks_called:
+      self.__postcall_hooks_called = True
+      apiproxy.GetPostCallHooks().Call(self.__service, self.__method,
+                                       self.request, self.response)
+
+  def get_result(self):
+    """Get the result of the RPC, or possibly raise an exception.
+
+    This implies a call to check_success().  If a get-result hook was
+    passed to make_call(), that hook is responsible for calling
+    check_success(), and the return value of the hook is returned.
+    Otherwise, check_success() is called directly and None is
+    returned.
+    """
+    if self.__get_result_hook is None:
+      self.check_success()
+      return None
+    else:
+      return self.__get_result_hook(self)
+
+
 def GetDefaultAPIProxy():
   try:
     runtime = __import__('google.appengine.runtime', globals(), locals(),
@@ -249,4 +450,5 @@
   except (AttributeError, ImportError):
     return APIProxyStubMap()
 
+
 apiproxy = GetDefaultAPIProxy()