| #!/usr/bin/env python | 
 | # | 
 | # Copyright 2007 Google Inc. | 
 | # | 
 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
 | # you may not use this file except in compliance with the License. | 
 | # You may obtain a copy of the License at | 
 | # | 
 | #     http://www.apache.org/licenses/LICENSE-2.0 | 
 | # | 
 | # Unless required by applicable law or agreed to in writing, software | 
 | # distributed under the License is distributed on an "AS IS" BASIS, | 
 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | # See the License for the specific language governing permissions and | 
 | # limitations under the License. | 
 | # | 
 |  | 
 |  | 
 |  | 
 |  | 
 | """Base class for implementing API proxy stubs.""" | 
 |  | 
 |  | 
 |  | 
 |  | 
 |  | 
 |  | 
 |  | 
 | from __future__ import with_statement | 
 |  | 
 |  | 
 |  | 
 | import random | 
 | import threading | 
 |  | 
 | from google.appengine.api import apiproxy_rpc | 
 | from google.appengine.api import request_info | 
 | from google.appengine.runtime import apiproxy_errors | 
 |  | 
 |  | 
 | MAX_REQUEST_SIZE = 1 << 20 | 
 |  | 
 |  | 
 | class APIProxyStub(object): | 
 |   """Base class for implementing API proxy stub classes. | 
 |  | 
 |   To implement an API proxy stub: | 
 |     - Extend this class. | 
 |     - Override __init__ to pass in appropriate default service name. | 
 |     - Implement service methods as _Dynamic_<method>(request, response). | 
 |   """ | 
 |  | 
 |  | 
 |  | 
 |   _ACCEPTS_REQUEST_ID = False | 
 |  | 
 |  | 
 |  | 
 |  | 
 |   THREADSAFE = False | 
 |  | 
 |   def __init__(self, service_name, max_request_size=MAX_REQUEST_SIZE, | 
 |                request_data=None): | 
 |     """Constructor. | 
 |  | 
 |     Args: | 
 |       service_name: Service name expected for all calls. | 
 |       max_request_size: int, maximum allowable size of the incoming request.  A | 
 |         apiproxy_errors.RequestTooLargeError will be raised if the inbound | 
 |         request exceeds this size.  Default is 1 MB. | 
 |       request_data: A request_info.RequestInfo instance used to look up state | 
 |         associated with the request that generated an API call. | 
 |     """ | 
 |     self.__service_name = service_name | 
 |     self.__max_request_size = max_request_size | 
 |     self.request_data = request_data or request_info._local_request_info | 
 |  | 
 |  | 
 |  | 
 |     self._mutex = threading.RLock() | 
 |     self.__error = None | 
 |     self.__error_dict = {} | 
 |  | 
 |   def CreateRPC(self): | 
 |     """Creates RPC object instance. | 
 |  | 
 |     Returns: | 
 |       a instance of RPC. | 
 |     """ | 
 |     return apiproxy_rpc.RPC(stub=self) | 
 |  | 
 |   def MakeSyncCall(self, service, call, request, response, request_id=None): | 
 |     """The main RPC entry point. | 
 |  | 
 |     Args: | 
 |       service: Must be name as provided to service_name of constructor. | 
 |       call: A string representing the rpc to make.  Must be part of | 
 |         the underlying services methods and impemented by _Dynamic_<call>. | 
 |       request: A protocol buffer of the type corresponding to 'call'. | 
 |       response: A protocol buffer of the type corresponding to 'call'. | 
 |       request_id: A unique string identifying the request associated with the | 
 |           API call. | 
 |     """ | 
 |     assert service == self.__service_name, ('Expected "%s" service name, ' | 
 |                                             'was "%s"' % (self.__service_name, | 
 |                                                           service)) | 
 |     if request.ByteSize() > self.__max_request_size: | 
 |       raise apiproxy_errors.RequestTooLargeError( | 
 |           'The request to API call %s.%s() was too large.' % (service, call)) | 
 |     messages = [] | 
 |     assert request.IsInitialized(messages), messages | 
 |  | 
 |  | 
 |  | 
 |  | 
 |     exception_type, frequency = self.__error_dict.get(call, (None, None)) | 
 |     if exception_type and frequency: | 
 |       if random.random() <= frequency: | 
 |         raise exception_type | 
 |  | 
 |     if self.__error: | 
 |       if random.random() <= self.__error_rate: | 
 |         raise self.__error | 
 |  | 
 |  | 
 |     method = getattr(self, '_Dynamic_' + call) | 
 |     if self._ACCEPTS_REQUEST_ID: | 
 |       method(request, response, request_id) | 
 |     else: | 
 |       method(request, response) | 
 |  | 
 |   def SetError(self, error, method=None, error_rate=1): | 
 |     """Set an error condition that may be raised when calls made to stub. | 
 |  | 
 |     If a method is specified, the error will only apply to that call. | 
 |     The error rate is applied to the method specified or all calls if | 
 |     method is not set. | 
 |  | 
 |     Args: | 
 |       error: An instance of apiproxy_errors.Error or None for no error. | 
 |       method: A string representing the method that the error will affect. | 
 |       error_rate: a number from [0, 1] that sets the chance of the error, | 
 |         defaults to 1. | 
 |     """ | 
 |     assert error is None or isinstance(error, apiproxy_errors.Error) | 
 |     if method and error: | 
 |       self.__error_dict[method] = error, error_rate | 
 |     else: | 
 |       self.__error_rate = error_rate | 
 |       self.__error = error | 
 |  | 
 |  | 
 | def Synchronized(method): | 
 |   """Decorator to acquire a mutex around an APIProxyStub method. | 
 |  | 
 |   Args: | 
 |     method: An unbound method of APIProxyStub or a subclass. | 
 |  | 
 |   Returns: | 
 |     The method, altered such it acquires self._mutex throughout its execution. | 
 |   """ | 
 |  | 
 |   def WrappedMethod(self, *args, **kwargs): | 
 |     with self._mutex: | 
 |       return method(self, *args, **kwargs) | 
 |  | 
 |   return WrappedMethod |