thirdparty/google_appengine/google/appengine/api/apiproxy_stub_map.py
changeset 2413 d0b7dac5325c
parent 828 f5fd65cc3bf3
child 2864 2e0b0af889be
equal deleted inserted replaced
2412:c61d96e72e6f 2413:d0b7dac5325c
    19 
    19 
    20 Classes/variables/functions defined here:
    20 Classes/variables/functions defined here:
    21   APIProxyStubMap: container of APIProxy stubs.
    21   APIProxyStubMap: container of APIProxy stubs.
    22   apiproxy: global instance of an APIProxyStubMap.
    22   apiproxy: global instance of an APIProxyStubMap.
    23   MakeSyncCall: APIProxy entry point.
    23   MakeSyncCall: APIProxy entry point.
       
    24   UserRPC: User-visible class wrapping asynchronous RPCs.
    24 """
    25 """
    25 
    26 
    26 
    27 
    27 
    28 
    28 
    29 
    29 
    30 
    30 import inspect
    31 import inspect
    31 import sys
    32 import sys
       
    33 
       
    34 from google.appengine.api import apiproxy_rpc
       
    35 
    32 
    36 
    33 def CreateRPC(service):
    37 def CreateRPC(service):
    34   """Creates a RPC instance for the given service.
    38   """Creates a RPC instance for the given service.
    35 
    39 
    36   The instance is suitable for talking to remote services.
    40   The instance is suitable for talking to remote services.
   158     for key, function, srv in self.__content:
   162     for key, function, srv in self.__content:
   159       if srv is None or srv == service:
   163       if srv is None or srv == service:
   160         function(service, call, request, response)
   164         function(service, call, request, response)
   161 
   165 
   162 
   166 
   163 class APIProxyStubMap:
   167 class APIProxyStubMap(object):
   164   """Container of APIProxy stubs for more convenient unittesting.
   168   """Container of APIProxy stubs for more convenient unittesting.
   165 
   169 
   166   Stubs may be either trivial implementations of APIProxy services (e.g.
   170   Stubs may be either trivial implementations of APIProxy services (e.g.
   167   DatastoreFileStub, UserServiceStub) or "real" implementations.
   171   DatastoreFileStub, UserServiceStub) or "real" implementations.
   168 
   172 
   200 
   204 
   201     Args:
   205     Args:
   202       service: string
   206       service: string
   203       stub: stub
   207       stub: stub
   204     """
   208     """
   205     assert not self.__stub_map.has_key(service)
   209     assert not self.__stub_map.has_key(service), repr(service)
   206     self.__stub_map[service] = stub
   210     self.__stub_map[service] = stub
   207 
   211 
   208     if service == 'datastore':
   212     if service == 'datastore':
   209       self.RegisterStub('datastore_v3', stub)
   213       self.RegisterStub('datastore_v3', stub)
   210 
   214 
   237     stub = self.GetStub(service)
   241     stub = self.GetStub(service)
   238     assert stub, 'No api proxy found for service "%s"' % service
   242     assert stub, 'No api proxy found for service "%s"' % service
   239     self.__precall_hooks.Call(service, call, request, response)
   243     self.__precall_hooks.Call(service, call, request, response)
   240     stub.MakeSyncCall(service, call, request, response)
   244     stub.MakeSyncCall(service, call, request, response)
   241     self.__postcall_hooks.Call(service, call, request, response)
   245     self.__postcall_hooks.Call(service, call, request, response)
       
   246 
       
   247 
       
   248 class UserRPC(object):
       
   249   """Wrapper class for asynchronous RPC.
       
   250 
       
   251   Simplest low-level usage pattern:
       
   252 
       
   253     rpc = UserRPC('service', [deadline], [callback])
       
   254     rpc.make_call('method', request, response)
       
   255     .
       
   256     .
       
   257     .
       
   258     rpc.wait()
       
   259     rpc.check_success()
       
   260 
       
   261   However, a service module normally provides a wrapper so that the
       
   262   typical usage pattern becomes more like this:
       
   263 
       
   264     from google.appengine.api import service
       
   265     rpc = service.create_rpc([deadline], [callback])
       
   266     service.make_method_call(rpc, [service-specific-args])
       
   267     .
       
   268     .
       
   269     .
       
   270     rpc.wait()
       
   271     result = rpc.get_result()
       
   272 
       
   273   The service.make_method_call() function sets a service- and method-
       
   274   specific hook function that is called by rpc.get_result() with the
       
   275   rpc object as its first argument, and service-specific value as its
       
   276   second argument.  The hook function should call rpc.check_success()
       
   277   and then extract the user-level result from the rpc.result
       
   278   protobuffer.  Additional arguments may be passed from
       
   279   make_method_call() to the get_result hook via the second argument.
       
   280   """
       
   281 
       
   282   __method = None
       
   283   __get_result_hook = None
       
   284   __user_data = None
       
   285   __postcall_hooks_called = False
       
   286 
       
   287   def __init__(self, service, deadline=None, callback=None):
       
   288     """Constructor.
       
   289 
       
   290     Args:
       
   291       service: The service name.
       
   292       deadline: Optional deadline.  Default depends on the implementation.
       
   293       callback: Optional argument-less callback function.
       
   294     """
       
   295     self.__service = service
       
   296     self.__rpc = CreateRPC(service)
       
   297     self.__rpc.deadline = deadline
       
   298     self.__rpc.callback = callback
       
   299 
       
   300   @property
       
   301   def service(self):
       
   302     """Return the service name."""
       
   303     return self.__service
       
   304 
       
   305   @property
       
   306   def method(self):
       
   307     """Return the method name."""
       
   308     return self.__method
       
   309 
       
   310   @property
       
   311   def deadline(self):
       
   312     """Return the deadline, if set explicitly (otherwise None)."""
       
   313     return self.__rpc.deadline
       
   314 
       
   315   def __get_callback(self):
       
   316     """Return the callback attribute, a function without arguments.
       
   317 
       
   318     This attribute can also be assigned to.  For example, the
       
   319     following code calls some_other_function(rpc) when the RPC is
       
   320     complete:
       
   321 
       
   322       rpc = service.create_rpc()
       
   323       rpc.callback = lambda: some_other_function(rpc)
       
   324       service.make_method_call(rpc)
       
   325       rpc.wait()
       
   326     """
       
   327     return self.__rpc.callback
       
   328   def __set_callback(self, callback):
       
   329     """Set the callback function."""
       
   330     self.__rpc.callback = callback
       
   331   callback = property(__get_callback, __set_callback)
       
   332 
       
   333   @property
       
   334   def request(self):
       
   335     """Return the request protocol buffer object."""
       
   336     return self.__rpc.request
       
   337 
       
   338   @property
       
   339   def response(self):
       
   340     """Return the response protocol buffer object."""
       
   341     return self.__rpc.response
       
   342 
       
   343   @property
       
   344   def state(self):
       
   345     """Return the RPC state.
       
   346 
       
   347     Possible values are attributes of apiproxy_rpc.RPC: IDLE, RUNNING,
       
   348     FINISHING.
       
   349     """
       
   350     return self.__rpc.state
       
   351 
       
   352   @property
       
   353   def get_result_hook(self):
       
   354     """Return the get-result hook function."""
       
   355     return self.__get_result_hook
       
   356 
       
   357   @property
       
   358   def user_data(self):
       
   359     """Return the user data for the hook function."""
       
   360     return self.__user_data
       
   361 
       
   362   def make_call(self, method, request, response,
       
   363                 get_result_hook=None, user_data=None):
       
   364     """Initiate a call.
       
   365 
       
   366     Args:
       
   367       method: The method name.
       
   368       request: The request protocol buffer.
       
   369       response: The response protocol buffer.
       
   370       get_result_hook: Optional get-result hook function.  If not None,
       
   371         this must be a function with exactly one argument, the RPC
       
   372         object (self).  Its return value is returned from get_result().
       
   373       user_data: Optional additional arbitrary data for the get-result
       
   374         hook function.  This can be accessed as rpc.user_data.  The
       
   375         type of this value is up to the service module.
       
   376 
       
   377     This function may only be called once per RPC object.  It sends
       
   378     the request to the remote server, but does not wait for a
       
   379     response.  This allows concurrent execution of the remote call and
       
   380     further local processing (e.g., making additional remote calls).
       
   381 
       
   382     Before the call is initiated, the precall hooks are called.
       
   383     """
       
   384     assert self.__rpc.state == apiproxy_rpc.RPC.IDLE, repr(self.state)
       
   385     self.__method = method
       
   386     self.__get_result_hook = get_result_hook
       
   387     self.__user_data = user_data
       
   388     apiproxy.GetPreCallHooks().Call(self.__service, method, request, response)
       
   389     self.__rpc.MakeCall(self.__service, method, request, response)
       
   390 
       
   391   def wait(self):
       
   392     """Wait for the call to complete, and call callbacks.
       
   393 
       
   394     This is the only time callback functions may be called.  (However,
       
   395     note that check_success() and get_result() call wait().)   Waiting
       
   396     for one RPC may cause callbacks for other RPCs to be called.
       
   397     Callback functions may call check_success() and get_result().
       
   398 
       
   399     Callbacks are called without arguments; if a callback needs access
       
   400     to the RPC object a Python nested function (a.k.a. closure) or a
       
   401     bound may be used.  To facilitate this, the callback may be
       
   402     assigned after the RPC object is created (but before make_call()
       
   403     is called).
       
   404 
       
   405     Note: don't confuse callbacks with get-result hooks or precall
       
   406     and postcall hooks.
       
   407     """
       
   408     assert self.__rpc.state != apiproxy_rpc.RPC.IDLE, repr(self.state)
       
   409     if self.__rpc.state == apiproxy_rpc.RPC.RUNNING:
       
   410       self.__rpc.Wait()
       
   411     assert self.__rpc.state == apiproxy_rpc.RPC.FINISHING, repr(self.state)
       
   412 
       
   413   def check_success(self):
       
   414     """Check for success of the RPC, possibly raising an exception.
       
   415 
       
   416     This function should be called at least once per RPC.  If wait()
       
   417     hasn't been called yet, it is called first.  If the RPC caused
       
   418     an exceptional condition, an exception will be raised here.
       
   419     The first time check_success() is called, the postcall hooks
       
   420     are called.
       
   421     """
       
   422     self.wait()
       
   423     self.__rpc.CheckSuccess()
       
   424     if not self.__postcall_hooks_called:
       
   425       self.__postcall_hooks_called = True
       
   426       apiproxy.GetPostCallHooks().Call(self.__service, self.__method,
       
   427                                        self.request, self.response)
       
   428 
       
   429   def get_result(self):
       
   430     """Get the result of the RPC, or possibly raise an exception.
       
   431 
       
   432     This implies a call to check_success().  If a get-result hook was
       
   433     passed to make_call(), that hook is responsible for calling
       
   434     check_success(), and the return value of the hook is returned.
       
   435     Otherwise, check_success() is called directly and None is
       
   436     returned.
       
   437     """
       
   438     if self.__get_result_hook is None:
       
   439       self.check_success()
       
   440       return None
       
   441     else:
       
   442       return self.__get_result_hook(self)
   242 
   443 
   243 
   444 
   244 def GetDefaultAPIProxy():
   445 def GetDefaultAPIProxy():
   245   try:
   446   try:
   246     runtime = __import__('google.appengine.runtime', globals(), locals(),
   447     runtime = __import__('google.appengine.runtime', globals(), locals(),
   247                          ['apiproxy'])
   448                          ['apiproxy'])
   248     return APIProxyStubMap(runtime.apiproxy)
   449     return APIProxyStubMap(runtime.apiproxy)
   249   except (AttributeError, ImportError):
   450   except (AttributeError, ImportError):
   250     return APIProxyStubMap()
   451     return APIProxyStubMap()
   251 
   452 
       
   453 
   252 apiproxy = GetDefaultAPIProxy()
   454 apiproxy = GetDefaultAPIProxy()